---
title: "SDK errors and pagination"
canonical: https://trickest.com/docs/developer-tools/sdk/errors-and-pagination
description: "Error taxonomy, retry behavior, idempotency, and list vs listPage pagination in @trickest/sdk."
---

# SDK errors and pagination

Examples use a server-side `client` from
[Client setup](/docs/developer-tools/sdk/client-setup), and resource IDs supplied
by your application.

## Error classes

HTTP error responses and fetch failures use **`TrickestError`** subclasses.
Branch with `instanceof`. Constructor checks and some service-level checks
throw ordinary errors; optional response validation can throw a Zod error.

| Class             | HTTP | When                                                |
| ----------------- | ---- | --------------------------------------------------- |
| `ValidationError` | 400  | Bad request body or SDK-side guard (e.g. TQL limit) |
| `AuthError`       | 401  | Missing, invalid, or expired token                  |
| `ForbiddenError`  | 403  | Authenticated but not allowed                       |
| `NotFoundError`   | 404  | Resource does not exist                             |
| `ConflictError`   | 409  | State conflict (duplicate, lock, …)                 |
| `RateLimitError`  | 429  | Rate limited; see `retryAfter`                      |
| `ServerError`     | 5xx  | Backend failure; safe to retry idempotent ops       |
| `NetworkError`    | n/a  | DNS, connection refused, TLS                        |
| `TimeoutError`    | n/a  | Exceeded `timeout` or `AbortSignal`                 |

Each error exposes:

- `status`; HTTP status (0 for network/timeout)
- `code`; machine-readable code string
- `requestId` / `traceId`; use in support and log correlation

```ts
import {
  TrickestError,
  NotFoundError,
  RateLimitError,
  ValidationError,
} from "@trickest/sdk";

async function getWorkflow(workflowId: string) {
  try {
    return await client.workflows.get(workflowId);
  } catch (err) {
    if (err instanceof NotFoundError) {
      return null;
    }
    if (err instanceof RateLimitError) {
      console.error("Retry delay in milliseconds:", err.retryAfter);
    }
    if (err instanceof ValidationError) {
      console.error(err.message, err.details);
    }
    if (err instanceof TrickestError) {
      console.error(err.status, err.code, err.traceId);
    }
    throw err;
  }
}
```

## Retry model

For JSON requests, retry is **on by default**: up to three retries after the
initial request, or four attempts total. Backoff is 1s, 2s, then 4s.

| Condition                                        | Retry behavior                                                                            |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------- |
| HTTP `429`                                       | Any method when retry is enabled; honors the `Retry-After` header, capped at 60s          |
| HTTP `5xx`, fetch/network failure, fetch timeout | Retries `GET`, `PUT`, `DELETE`, `HEAD`, and `OPTIONS`, or a request with `idempotencyKey` |
| Other HTTP errors, including `400` and `401`     | No automatic retry                                                                        |
| Response schema failure                          | No automatic retry                                                                        |
| Multipart uploads and `http.stream()`   | No automatic retry loop                                                                   |

```ts
const client = new TrickestClient({
  token: "…",
  retry: { maxRetries: 5, baseDelay: 500 },
});

const noRetry = new TrickestClient({ token: "…", retry: false });

await client.runs.execute(workflowId, { retry: false });
```

An idempotency key permits write retries; the endpoint must implement
deduplication to prevent duplicate effects. Explicit cancellation may still
pass through the retry loop. Use `retry: false` when cancellation must
avoid additional attempts. `retryAfter` is milliseconds when present.

## Pagination

Most list operations expose two shapes:

### `list()` returns an `AsyncGenerator`

Auto-fetches pages until exhausted. Default page size is often **500**.

```ts
for await (const wf of client.workflows.list(spaceId)) {
  console.log(wf.name);
}
```

### `listPage()` returns one page

Returns `PaginatedResponse<T>`:

```ts
interface PaginatedResponse<T> {
  results: T[];
  next: string | null;
  count: number;
}

const page = await client.workflows.listPage(spaceId, {
  pageSize: 20,
  page: 1,
});
console.log(page.results.length, "of", page.count);
```

### Services with both patterns

| Namespace   | `list`                | `listPage` |
| ----------- | --------------------- | ---------- |
| `spaces`    | yes                   | yes        |
| `workflows` | yes (needs `spaceId`) | yes        |
| `runs`      | yes                   | yes        |
| `storage`   | yes                   | yes        |
| `variables` | yes                   | yes        |
| `fleet`     | yes                   | n/a        |
| `library`   | `search()` generator  | n/a        |
| `memory`    | yes                   | n/a        |
| `audit`     | yes (needs `vaultId`) | n/a        |

Some services only expose one shape; check the [method index](/docs/developer-tools/sdk/method-index).

## Related

<CardGroup cols={2}>
  <Card
    title="Client setup"
    icon="gear"
    href="/docs/developer-tools/sdk/client-setup"
  >
    `RequestOptions`, idempotency keys, timeouts.
  </Card>
  <Card
    title="Database"
    icon="database"
    href="/docs/developer-tools/sdk/database"
  >
    `query` / `queryAll` pagination and TQL limits.
  </Card>
</CardGroup>

---
_Markdown source of https://trickest.com/docs/developer-tools/sdk/errors-and-pagination._
