> ## 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

> Conventions shared by every TestDino API endpoint: authentication, rate limits, response format, error codes, and pagination.

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 read · 60/min write · 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).

Read endpoints work for any role with project access. Write endpoints (POST/PATCH) additionally require the token owner to hold a writer role (owner, admin, or member) on the project's organization; a viewer-role token can read but gets `403 FORBIDDEN` on any write. Write permission follows the owner's current org role, not a setting on the token.

<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 |
| :- | :- | :- |
| Reads, per token | 100 requests | 1 minute |
| Writes (POST/PATCH), per token | 60 requests | 1 minute |
| Manual run creation, per token | 10 requests | 1 minute |
| Per IP (pre-auth) | 200 requests | 1 minute |
| PDF generation | 1 request | 1 minute (per token) |

Authenticated responses include standard rate-limit headers. Requests rejected before authentication, such as a `401`, do not carry them.

| 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 |
| 403 | `FORBIDDEN` | Token owner lacks a writer role for this write | Use a token whose owner is owner/admin/member on the org |
| 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>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.