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

# Pagination, errors & rate limits

> The list envelope, the error envelope, and how rate limiting behaves.

## Pagination

List endpoints take two query parameters and return a consistent envelope:

| Param    | Meaning                                      | Default                        |
| -------- | -------------------------------------------- | ------------------------------ |
| `offset` | Zero-based index of the first item to return | `0`                            |
| `top`    | Maximum number of items to return            | `100` (connections list: `25`) |

```json theme={null}
{
  "items": [ ... ],
  "totalCount": 137,
  "offset": 0,
  "top": 100
}
```

All four fields are always present. Page until `offset + items.length >= totalCount`. Both SDKs auto-paginate internally - their `list`-style methods return the complete array.

## Error envelope

Errors raised by the API carry a consistent JSON body:

```json theme={null}
{
  "errorCode": "VALIDATION_ERROR",
  "message": "Human-readable error message.",
  "requestId": "0HN0...",
  "timestamp": "2026-08-12T10:00:00Z",
  "path": "/v1/tools/monday_create_item/execute",
  "details": [
    { "field": "fields.name", "issue": "Required field is missing." }
  ]
}
```

`details` appears only on field-level validation failures (`400`, `errorCode: "VALIDATION_ERROR"`).

| Status | Meaning                                                                     |
| ------ | --------------------------------------------------------------------------- |
| 400    | Bad request - malformed body or invalid parameters                          |
| 401    | Unauthorized - missing or invalid credentials                               |
| 403    | Forbidden - the caller lacks access to the resource                         |
| 404    | Not found - the resource does not exist                                     |
| 409    | Conflict - e.g. no default connection configured for the tool's application |
| 429    | Too many requests - rate limit exceeded                                     |
| 500    | Internal server error - include `requestId` when reporting                  |

## Error codes

Branch on `errorCode` rather than parsing `message` - messages may be reworded, codes are contract. The ones you're most likely to handle:

| `errorCode`                 | Status | Meaning                                                                                   |
| --------------------------- | ------ | ----------------------------------------------------------------------------------------- |
| `VALIDATION_ERROR`          | 400    | Request failed validation - see `details[]` for the offending fields                      |
| `MALFORMED_BODY`            | 400    | Body missing or unparseable                                                               |
| `WRONG_APPLICATION`         | 400    | The connection you passed belongs to a different application than the tool                |
| `NOT_FOUND`                 | 404    | The connection, tool, or toolset doesn't exist (or isn't yours)                           |
| `NO_DEFAULT_CONNECTION`     | 409    | No default connection for the tool's application - pass `?connectionId=` or set a default |
| `NO_TOOLSET_CONNECTION`     | 409    | The toolset has no connection for the tool's application                                  |
| `TOOL_NOT_IN_TOOLSET`       | 403    | The tool isn't in the toolset's allow-list                                                |
| `CONNECTION_NOT_IN_TOOLSET` | 403    | The connection isn't part of the toolset you scoped to                                    |
| `ACCOUNT_USER_INACTIVE`     | 403    | The acting user is no longer active on the account                                        |

New codes may be added over time - treat an unrecognized `errorCode` as a generic failure of its HTTP status rather than erroring out.

<Note>
  **Rejections that happen before your request reaches the API** - an invalid or missing credential (`401`) and unmatched routes (`404`) - currently return an **empty body**. Don't parse the body to detect auth failure; branch on the status code, and use the `x-request-id` response header (returned on every request) when contacting support.
</Note>

<Note>
  `POST /v1/tools/{toolSlug}/execute` is special: **tool-runtime** failures (the downstream API errored) return `200` with `isSuccess: false` and an `errorMessage` - not the error envelope. Branch on `isSuccess`, not just the HTTP status.
</Note>

## Rate limits

Limits are enforced **per account** with two budgets that must both pass: a per-second burst and a sustained hourly window. Search-backed discovery calls (`GET /v1/tools` or `GET /v1/applications` with a non-empty `?search=`) have their own, stricter budget because they trigger semantic search.

On `429`, honor the `Retry-After` response header (seconds): `1` when the burst window tripped, `3600` when the hourly window tripped. Back off and retry - don't hammer.
