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

# Errors and pagination

> What the API returns when something is wrong, when to retry, and how to walk a long list.

## Error envelope

Every non-2xx response has the same shape:

```json theme={null}
{
  "error": "Invalid agent_id format"
}
```

The HTTP status code carries the *class* of failure; the `error` string is a
human-readable hint. Don't pattern-match on the message text; it's free-form and may
change. Branch on the status code instead.

## Status code reference

| Status                      | Meaning                                                                  | What to do                                                                                                   |
| --------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| `400 Bad Request`           | Malformed request: invalid UUID, missing required field, oversized file. | Fix the request; do not retry as-is.                                                                         |
| `401 Unauthorized`          | API key missing, malformed, or revoked.                                  | Mint a new key (see [Authentication](/docs/authentication)).                                                      |
| `403 Forbidden`             | Key is valid but the agent/task lives in another workspace.              | Use a key scoped to the right workspace.                                                                     |
| `404 Not Found`             | Agent or task UUID doesn't exist.                                        | Check the ID. Note: this is also returned for resources in other workspaces, so a stolen ID can't be probed. |
| `409 Conflict`              | State-conflict (e.g. publishing an agent that has no draft changes).     | Inspect the message; the operation isn't valid right now.                                                    |
| `429 Too Many Requests`     | Rate limit hit.                                                          | Back off (see below).                                                                                        |
| `500 Internal Server Error` | Unexpected server-side error.                                            | Safe to retry with exponential backoff.                                                                      |
| `503 Service Unavailable`   | Temporary outage / deploy.                                               | Retry with backoff.                                                                                          |

## When to retry

| Class              | Retry?                                                                                                                                 |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `4xx` except `429` | No. The request itself is wrong; fix and re-send.                                                                                      |
| `429`              | Yes, after a back-off. Respect any `Retry-After` header if present.                                                                    |
| `5xx`              | Yes, these are transient. Use exponential backoff (1s → 2s → 5s → 10s, cap at \~30s) with full jitter, and give up after 4–5 attempts. |

A minimal retry helper:

```python Python theme={null}
import os, time, random, requests

def with_retries(method, url, *, max_attempts=5, **kwargs):
    delay = 1.0
    for attempt in range(max_attempts):
        r = requests.request(method, url, **kwargs)
        if r.status_code < 500 and r.status_code != 429:
            return r
        if attempt == max_attempts - 1:
            r.raise_for_status()
        retry_after = r.headers.get("Retry-After")
        sleep = float(retry_after) if retry_after else min(delay, 30) * (1 + random.random())
        time.sleep(sleep)
        delay *= 2
```

<Warning>
  Never retry `POST /v1/agents/{agent_id}/run` after a 5xx without checking whether
  the task was actually created. A 5xx during run-creation can occasionally mean
  "request reached us, then the connection died", so issue a `GET /v1/tasks` filtered
  by `agent_id` and `created_after` before retrying to avoid double-runs.
</Warning>

## Pagination

List endpoints (`GET /v1/tasks`, `GET /v1/agents`, `GET /v1/agents/{id}/versions`) are
cursor-paginated, newest-first. The response looks like:

```json theme={null}
{
  "tasks": [ /* … up to `limit` items … */ ],
  "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0xMlQwOToxNTowMFoiLCJpZCI6IjFhMmIzYy0..."
}
```

To fetch the next page, pass that cursor back unchanged as the `cursor` query parameter.
When you reach the end, `next_cursor` is omitted (or empty).

```bash theme={null}
# First page
curl "https://agents.nanonets.com/api/v1/tasks?limit=50" \
  -H "Authorization: Bearer $NANONETS_API_KEY"

# Subsequent page
curl "https://agents.nanonets.com/api/v1/tasks?limit=50&cursor=$NEXT_CURSOR" \
  -H "Authorization: Bearer $NANONETS_API_KEY"
```

```python Python theme={null}
def iter_tasks(**filters):
    cursor = None
    while True:
        params = {"limit": 100, **filters}
        if cursor:
            params["cursor"] = cursor
        r = requests.get(f"{BASE}/api/v1/tasks", headers=HEADERS, params=params).json()
        for task in r["tasks"]:
            yield task
        cursor = r.get("next_cursor")
        if not cursor:
            return

for task in iter_tasks(agent_id="…", status="completed"):
    print(task["task_id"], task["title"])
```

### Cursor opacity

`next_cursor` is opaque; don't try to parse it. If you decode it today and rely on the
shape, your code will break the next time the server changes its pagination key.

### `limit`

Default is `20`, max is `100`. Larger pages mean fewer round-trips but a larger response
body; for bulk export, `100` is usually the right choice.

## Rate limits

Rate limits are enforced per-workspace, not per-key. If you're running tight loops or
fanning out many concurrent agents, watch for `429` and back off. We do not currently
publish per-tier numbers. If you're hitting limits in production, [reach out](https://nanonets.com/support).
