> ## Documentation Index
> Fetch the complete documentation index at: https://docs.testdino.com/llms.txt
> Use this file to discover all available pages before exploring further.

# TestDino API Conventions

> TestDino API standards: auth, rate limits, errors, pagination, and filtering.

Every endpoint in the TestDino Public API follows the same conventions for authentication, response shape, errors, and pagination.

## Quick reference

| Topic                               | Summary                                                  |
| :---------------------------------- | :------------------------------------------------------- |
| [Authentication](#authentication)   | Bearer PAT (`td_pat_` prefix) scoped to the project      |
| [Rate limits](#rate-limits)         | 100/min per token · 200/min per IP · 1/min for PDF       |
| [Response format](#response-format) | `{ success, data, pagination? }` or `{ success, error }` |
| [Errors](#errors)                   | Uniform error codes with human-readable messages         |
| [Pagination](#pagination)           | `page` + `limit` query params, `pagination` in response  |
| [Date filtering](#date-filtering)   | Endpoint-specific, see table below                       |

## Authentication

All endpoints require a Bearer token in the `Authorization` header:

```http theme={null}
Authorization: Bearer td_pat_your_token_here
```

| Requirement     | Value                                                         |
| :-------------- | :------------------------------------------------------------ |
| Token type      | Personal access token (PAT)                                   |
| Token prefix    | `td_pat_`                                                     |
| Required access | The token must grant access to the project in the request URL |
| Header format   | `Authorization: Bearer td_pat_...`                            |
| Storage         | Environment variable, secret manager, never commit to source  |

Create a PAT from **User Settings → Personal Access Tokens**. A PAT carries no permission scopes: you grant it access to specific organizations and projects when you create it. See [Generate API keys](/guides/generate-api-keys).

<Note>
  **Note**

  Tokens can be rotated or revoked from **User Settings → Personal Access Tokens** at any time. A revoked token stops working immediately and returns `401 TOKEN_REVOKED`.
</Note>

## Rate limits

| Scope             | Limit        | Window               |
| :---------------- | :----------- | :------------------- |
| Per token         | 100 requests | 1 minute             |
| Per IP (pre-auth) | 200 requests | 1 minute             |
| PDF generation    | 1 request    | 1 minute (per token) |

Every response includes standard rate-limit headers:

| Header                | Meaning                                    |
| :-------------------- | :----------------------------------------- |
| `RateLimit-Limit`     | Max requests allowed in the current window |
| `RateLimit-Remaining` | Requests remaining in the window           |
| `RateLimit-Reset`     | Seconds until the window resets            |

When you exceed a limit, the API returns `429 RATE_LIMIT_EXCEEDED`. PDF-specific 429 responses also include a `Retry-After` header indicating how many seconds to wait.

```json theme={null}
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests"
  }
}
```

<Tip>
  **Tip**

  For batch work, back off to 1 request/second (60/min) to leave headroom. If you need more, throttle on `RateLimit-Remaining` and pause when it drops below 10.
</Tip>

## Response format

Every successful response uses this envelope:

```json theme={null}
{
  "success": true,
  "data": { /* endpoint-specific payload */ },
  "pagination": { /* present on list endpoints only */ }
}
```

Every error response uses this envelope:

```json theme={null}
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description"
  }
}
```

| Field           | Type            | Description                                                |
| :-------------- | :-------------- | :--------------------------------------------------------- |
| `success`       | boolean         | `true` for 2xx responses, `false` otherwise                |
| `data`          | object or array | Endpoint payload (omitted on error)                        |
| `pagination`    | object          | Present on list endpoints, see [Pagination](#pagination)   |
| `error.code`    | string          | Stable machine-readable code, safe to branch on            |
| `error.message` | string          | Human-readable description, safe to log, not safe to parse |

## Errors

Branch your client on `error.code`, not HTTP status alone. Codes are stable; messages may change.

| HTTP | Error code            | When it happens                                          | Action                                              |
| :--- | :-------------------- | :------------------------------------------------------- | :-------------------------------------------------- |
| 400  | `VALIDATION_ERROR`    | Missing or invalid query/path parameter                  | Fix the request, don't retry                        |
| 401  | `UNAUTHORIZED`        | Header missing, wrong prefix, or token not recognized    | Fix the header, re-issue the token                  |
| 401  | `TOKEN_REVOKED`       | The token was revoked                                    | Create a new token                                  |
| 401  | `TOKEN_EXPIRED`       | The token passed its expiry date                         | Create a new token                                  |
| 404  | `NOT_FOUND`           | Resource doesn't exist or isn't accessible to this token | Verify the ID, confirm the token covers the project |
| 429  | `RATE_LIMIT_EXCEEDED` | Too many requests in the window                          | Back off using `RateLimit-Reset` / `Retry-After`    |
| 5xx  | `INTERNAL_ERROR`      | Server-side failure                                      | Retry with exponential backoff                      |

<Tip>
  **Tip**

  Retry only on `429` and `5xx`. Use exponential backoff starting at 1 second, capped at 30 seconds, with jitter. Never retry `400`, `401`, `403`, or `404`.
</Tip>

## Pagination

List endpoints accept `page` and `limit`. **`limit` is not an arbitrary 1–100 range**. Most run/analytics lists only accept `10`, `25`, `50`, or `100` (default `10`). Sending `20` returns `400 INVALID_LIMIT`:

| Parameter | Type    | Default                       | Range                                                                                                   |
| :-------- | :------ | :---------------------------- | :------------------------------------------------------------------------------------------------------ |
| `page`    | integer | 1                             | ≥ 1                                                                                                     |
| `limit`   | integer | 10 (runs) / endpoint-specific | Runs & most DH lists: `10` \| `25` \| `50` \| `100`. Explorer: `10` \| `25` \| `50`. See each endpoint. |

Pagination metadata is included in the response:

```json theme={null}
{
  "success": true,
  "data": [ /* items for this page */ ],
  "pagination": {
    "page": 1,
    "limit": 10,
    "total": 134,
    "hasNext": true,
    "hasPrev": false
  }
}
```

Iterate all pages in Node.js:

```javascript theme={null}
let page = 1;
const all = [];
while (true) {
  const res = await fetch(
    `${BASE_URL}/${projectId}/test-runs?page=${page}&limit=100`,
    { headers: { Authorization: `Bearer ${token}` } }
  );
  const body = await res.json();
  all.push(...body.data);
  if (!body.pagination.hasNext) break;
  page += 1;
}
```

<Note>
  **Note**

  A few endpoints use `offset` + `limit` instead of `page` + `limit` (e.g. `GET /test-cases/history`). The parameter name is documented on each endpoint's reference page.
</Note>

## Date filtering

There is **no global date-filter convention**. Each endpoint documents its own parameters:

| Endpoint family                                                     | Parameter                | Allowed values                                  |
| :------------------------------------------------------------------ | :----------------------- | :---------------------------------------------- |
| [Test runs list](/api-reference/endpoints/test-runs/list-test-runs) | `start_date`, `end_date` | RFC3339 timestamps, **both required together**  |
| Explorer, specs, analytics                                          | `days`                   | Integer snapped to **7**, **30**, or **90**     |
| Dashboard, filters                                                  | —                        | **No query params** (fixed downstream snapshot) |

**Test runs** do not accept `period`, `dateRange`, or camelCase `startDate`/`endDate`. Use snake\_case `start_date`/`end_date`.

**Rolling windows** (`days` on explorer/specs/analytics): values other than 7, 30, or 90 are snapped to the nearest allowed window (default **30** when absent or invalid). String presets like `dateRange=7d` are **not** supported on analytics. Pass `days=7` instead.

<Note>
  **Note**

  Sending undocumented query keys on public routes is stripped at the gateway, not forwarded silently. Check each endpoint's reference page for the exact allowlist.
</Note>
