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

# Errors & Limits

> Status codes, error shapes, timeouts, and concurrency guidance for the Agent API

## Error shape

Errors before the stream opens are JSON, in Anthropic's shape:

```json theme={null}
{ "error": { "type": "not_found_error", "message": "Agent not found in this workspace" } }
```

Errors *after* the stream opens arrive as an SSE event on an already-`200`
response:

```
event: error
data: {"type":"error","error":{"type":"api_error","message":"…"}}
```

<Warning>
  A `200` status only means the turn started. Always handle `error` events, and
  treat a stream that ends without `message_stop` as a failed turn.
</Warning>

***

## Status codes

| Status | `error.type`            | Cause                                                              | Fix                                                                        |
| ------ | ----------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------- |
| `400`  | `invalid_request_error` | Missing body, empty `messages`, or no user-role text in `messages` | Send a non-empty `messages` array with at least one `role: "user"` entry   |
| `401`  | `authentication_error`  | Missing, unknown, or malformed key                                 | Check the `X-Api-Key` header                                               |
| `401`  | —                       | `{"message": "API key has been revoked"}`                          | Create a new key                                                           |
| `401`  | credential error        | The agent has no usable LLM credential                             | Add the agent's model credential in the workspace                          |
| `403`  | `permission_error`      | Viewer role, or writing to a shared conversation you don't own     | Use an Operator+ key; fork the conversation                                |
| `404`  | `not_found_error`       | `Agent not found in this workspace`                                | Check the agent slug **and** that the key belongs to the agent's workspace |
| `404`  | `not_found_error`       | `Conversation not found`                                           | The id belongs to another agent, workspace, or user                        |
| `404`  | HTML page               | Wrong HTTP verb (`GET` on `/v1/messages`)                          | Routes are method-specific — use the documented verb                       |
| `409`  | `conflict_error`        | Fork target id already taken                                       | Omit `conversation_id` on fork, or pick a new one                          |
| `413`  | —                       | Request body over 15 MiB                                           | Reduce attachment size (10 MiB decoded cap)                                |
| `501`  | `not_implemented`       | `metadata.delivery: "async"`                                       | Use webhooks or schedules for background runs                              |

<Accordion title="Why a workspace mismatch shows up as 404, not 403">
  Agent lookup is scoped to the calling key's workspace, and a miss is reported
  the same way whether the agent doesn't exist or lives somewhere you can't see —
  so no caller can probe for agent names in other workspaces. If you're sure the
  name is right, the key is almost always the problem.
</Accordion>

***

## Limits and timing

|                        |                                                    |
| ---------------------- | -------------------------------------------------- |
| **Per-turn ceiling**   | 10 minutes of agent execution                      |
| **Typical short turn** | \~10–20 s, dominated by sandbox and agent startup  |
| **Attachments**        | 10 files, 10 MiB decoded total, per turn           |
| **Request body**       | 15 MiB                                             |
| **Rate limit**         | None on this endpoint — bound concurrency yourself |

A long-running turn that is producing output or doing tool work stays alive; the
runtime only cancels a turn that has genuinely lost its network or exceeded the
ceiling. A turn cancelled that way keeps its resume pointer, so sending
`"continue"` on the same conversation picks up where it stopped.

### Client configuration

* **Set generous timeouts.** Default HTTP client timeouts (30 s) will cut off
  normal agent work. Allow at least 10 minutes and read the stream incrementally.
* **Disable buffering proxies.** Any intermediary that buffers responses breaks
  streaming. The endpoint sends `Cache-Control: no-cache, no-transform`.
* **Retry on the conversation, not the request.** Because a resumed turn appends
  to durable history, a blind retry of a turn that partially succeeded can double
  up work. Prefer sending a fresh instruction on the same `conversation_id`.
* **Wait for the resume pointer between back-to-back turns.** Sending the next
  message the moment a stream ends can resume a conversation before its session
  is recorded, and the agent loses the previous turn's context silently. See
  [Resuming](/api-reference/agent-api/conversations#resuming).

### Concurrency

Each conversation runs in one sandbox. Two simultaneous turns on the same
`conversation_id` share it and race on the transcript.

<Tip>
  Serialize turns **within** a conversation; parallelize **across** conversations.
  A queue keyed by `conversation_id` is the simplest correct client design.
</Tip>

***

## Observability

Every turn records an execution on the agent — status, duration, and result — with
API-key traffic tagged separately from web-UI traffic, so you can filter your
integration's runs on the agent's activity page. Failed turns keep their error
message there, which is usually faster than reconstructing it from the stream.
