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

# API Pagination, Error Handling & Response Shapes

> Understand how Terminus Hub API list endpoints paginate results and how every error response is structured, with types, codes, and example payloads.

Every list endpoint in the Terminus Hub Public API returns results using a consistent pagination envelope, and every error (regardless of the endpoint or failure reason) uses the same error object structure. Learning these two patterns once means you can handle them uniformly across your entire integration.

***

## Pagination

All list endpoints (`GET /api/v1/workspaces`, `GET /api/v1/records`, etc.) return a shared response envelope containing the page of results, a flag indicating whether more pages exist, and the total count of matching resources.

### Query Parameters

Control pagination by passing the following query parameters on any list request:

<ParamField query="page" type="integer" default="1">
  The page number to retrieve. Pages are 1-indexed. The first page is `page=1`. Omit this parameter to start from the beginning.
</ParamField>

<ParamField query="limit" type="integer" default="100">
  The number of items to return per page. Must be between **1** and **10000** (inclusive). Defaults to `100` if not specified.
</ParamField>

### Example Paginated Request

The following request fetches the second page of records, with 50 results per page:

```bash theme={null}
curl "https://hub.terminus.app/api/v1/records?page=2&limit=50" \
  -H "Authorization: Bearer thub_xxxxx" \
  -H "Content-Type: application/json"
```

### Pagination Response Envelope

Every list response wraps results in the same top-level envelope:

```json theme={null}
{
  "items": [
    {
      "id": "9b2c1f4e-7a3d-4c8e-9f1a-2b6d5e4c3a10",
      "status": "active",
      "terminus_id": "trm0kf8m2a1q",
      "data": {}
    }
  ],
  "has_more": true,
  "total_count": 142
}
```

<ResponseField name="items" type="array" required>
  The array of resource objects for the current page. The shape of each item depends on the endpoint (Workspace, Record, Submission, etc.).
</ResponseField>

<ResponseField name="has_more" type="boolean" required>
  `true` when there are additional pages beyond the current one. When `false`, the current page is the last page of results.
</ResponseField>

<ResponseField name="total_count" type="integer" required>
  The total number of resources matching the request's filters, across all pages, not just the current page. Use this to calculate the total number of pages: `ceil(total_count / limit)`.
</ResponseField>

<Tip>
  To retrieve all results programmatically, increment `page` by 1 on each request and stop when `has_more` is `false`. Larger pages mean fewer requests; the maximum `limit` is `10000`.
</Tip>

***

## Errors

When a request fails, the API returns an appropriate HTTP status code and a JSON body containing a single `error` object. The structure is identical regardless of the error type, making it easy to write a single error-handling function for your entire integration.

### Error Envelope

```json theme={null}
{
  "error": {
    "type": "not_found_error",
    "code": null,
    "message": "Record not found",
    "param": null,
    "path": null
  }
}
```

### Error Response Fields

<ResponseField name="error" type="object" required>
  The top-level error container. All error details are nested inside this object.

  <Expandable title="error fields">
    <ResponseField name="type" type="enum" required>
      A broad category for the error. One of: `invalid_request_error`, `authentication_error`, `authorization_error`, `not_found_error`, `rate_limit_error`, `plan_limit_exceeded`, or `api_error`. Use this field to branch your error-handling logic.
    </ResponseField>

    <ResponseField name="code" type="string | null" required>
      A specific, machine-readable code within the `type` category when one applies (e.g., `missing_param`, `feature_required`, `limit_exceeded`, `rate_limit_exceeded`), or `null` when the error has no finer-grained code (most authentication and not-found errors). Suitable for programmatic handling or logging.
    </ResponseField>

    <ResponseField name="message" type="string" required>
      A human-readable description of the error explaining what went wrong. Suitable for displaying in logs or developer-facing UI, but not intended for end-user display.
    </ResponseField>

    <ResponseField name="param" type="string | null" required>
      When the error relates to a specific request parameter (e.g., a missing required field), this field names that parameter. `null` when the error is not tied to a specific input field.
    </ResponseField>

    <ResponseField name="path" type="string | null" required>
      When the error points at a specific location inside the request body, this field holds a path to it. `null` when not applicable.
    </ResponseField>
  </Expandable>
</ResponseField>

### Error Types Reference

| Type                    | HTTP Status | Description                                                                                                                                                                                                   |
| ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_request_error` | 422         | The request was understood but a value is invalid, out of range, or fails a validation rule. Check the `param` field to identify the offending input. (A genuinely missing required parameter returns `400`.) |
| `authentication_error`  | 401         | The API key is missing, incorrectly formatted, or has been revoked. See [Authentication](/api-reference/authentication) for guidance.                                                                         |
| `authorization_error`   | 403         | The API key is valid but does not have permission to access the requested resource. Verify the key belongs to an account with the necessary access.                                                           |
| `not_found_error`       | 404         | The requested resource does not exist. Verify the ID in the URL is correct and belongs to a resource your key can access.                                                                                     |
| `rate_limit_error`      | 429         | Too many requests have been made in a short period. Back off and retry after the delay in the `Retry-After` header.                                                                                           |
| `plan_limit_exceeded`   | 422         | The request needs a feature or exceeds a limit not included in your current plan. Check `param` for the feature or resource, then upgrade to proceed.                                                         |
| `api_error`             | 500         | An unexpected error occurred on the Terminus Hub server. These are rare. If they persist, contact Terminus Hub support.                                                                                       |

<Note>
  Your integration should always check the HTTP status code first, then inspect `error.type` for branching logic, and `error.code` for fine-grained handling. Do not rely solely on `error.message`, as message text may change between API versions.
</Note>

### Handling Errors: Example

The snippet below demonstrates a simple, robust error-handling pattern in JavaScript:

```javascript theme={null}
const response = await fetch(
  "https://hub.terminus.app/api/v1/records/rec_missing",
  {
    headers: { Authorization: "Bearer thub_xxxxx" },
  }
);

if (!response.ok) {
  const { error } = await response.json();

  switch (error.type) {
    case "authentication_error":
      console.error("Check your API key:", error.message);
      break;
    case "not_found_error":
      console.error("Resource not found:", error.message);
      break;
    case "rate_limit_error":
      console.warn("Rate limited, backing off...");
      // implement retry with exponential back-off
      break;
    default:
      console.error(`API error [${error.code}]:`, error.message);
  }
}
```

***

## Rate limiting

Requests are rate limited per API key. Each key may make up to **100 requests per minute**. (Unauthenticated requests are limited to 20 per minute per IP.)

When you exceed the limit, the API responds with `429` and a `rate_limit_error`:

```json theme={null}
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "Too many requests. Please retry after 42 seconds.",
    "param": null
  }
}
```

Every `429` includes headers to help you back off:

| Header                  | Meaning                             |
| ----------------------- | ----------------------------------- |
| `Retry-After`           | Seconds to wait before retrying     |
| `X-RateLimit-Limit`     | The request ceiling for the window  |
| `X-RateLimit-Remaining` | Requests left in the current window |
| `X-RateLimit-Reset`     | Unix time when the window resets    |

Respect `Retry-After` and implement exponential back-off for resilient integrations.
