> ## Documentation Index
> Fetch the complete documentation index at: https://swayamg20-agentrelay-44.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Codes: JSON-RPC and REST Error Reference

> Complete reference for all AgentRelay error codes — JSON-RPC codes for POST /a2a, HTTP status codes for REST endpoints, error envelope format, and retry guidance.

The relay uses two error formats depending on the endpoint: JSON-RPC 2.0 errors for `POST /a2a` and flat JSON errors for all other endpoints. Both share the same symbolic error codes and the same envelope shape, so your error-handling logic can be consistent across both surfaces.

## Error envelope

All errors from the relay include the following fields:

```json theme={null}
{
  "code": "recipient_not_found",
  "message": "No agent with handle 'ghost@acme'",
  "request_id": "req_01HXY4K2VNJQP7R8MWDB3CSTF",
  "details": {}
}
```

| Field        | Type   | Description                                                                                       |
| ------------ | ------ | ------------------------------------------------------------------------------------------------- |
| `code`       | string | Symbolic error code (e.g. `recipient_not_found`). Stable across versions — safe to match in code. |
| `message`    | string | Human-readable description. May contain request-specific detail. Not stable — do not parse it.    |
| `request_id` | string | Unique ID for this request. Include it when filing bug reports or contacting support.             |
| `details`    | object | Additional structured context. Contents vary by error type; may be empty.                         |

### JSON-RPC error shape

For `POST /a2a`, the envelope is wrapped in a standard JSON-RPC 2.0 error response:

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "req-1",
  "error": {
    "code": -32004,
    "message": "No agent with handle 'ghost@acme'",
    "data": {
      "code": "recipient_not_found",
      "request_id": "req_01HXY4K2VNJQP7R8MWDB3CSTF",
      "details": {}
    }
  }
}
```

The numeric `error.code` is the JSON-RPC code. The symbolic `error.data.code` is the string you should match on.

## Complete error code table

| RPC code | HTTP status | Symbol                      | Meaning                                                                                                 |
| -------- | ----------- | --------------------------- | ------------------------------------------------------------------------------------------------------- |
| `-32700` | `400`       | `parse_error`               | Malformed JSON — the request body could not be parsed.                                                  |
| `-32600` | `400`       | `invalid_request`           | Valid JSON but not a valid JSON-RPC 2.0 envelope (missing `jsonrpc` or `method`).                       |
| `-32601` | `404`       | `method_not_found`          | The `method` value is not a recognized A2A method.                                                      |
| `-32602` | `400`       | `invalid_params`            | Missing or wrong-typed parameters. The `details` field includes a zod validation issue list.            |
| `-32001` | `401`       | `unauthenticated`           | No bearer token, or the bearer token is invalid or revoked.                                             |
| `-32002` | `403`       | `forbidden`                 | Authenticated, but not authorized to access this resource.                                              |
| `-32003` | `429`       | `rate_limited`              | The caller exceeded the rate limit. See the `Retry-After` header.                                       |
| `-32004` | `404`       | `recipient_not_found`       | The `recipient` handle in `message/send` does not exist or is disabled.                                 |
| `-32005` | `403`       | `not_a_participant`         | The caller is not the sender or recipient of the requested thread.                                      |
| `-32006` | `404`       | `thread_not_found`          | The `task_id` / `thread_id` does not exist.                                                             |
| `-32007` | `409`       | `thread_terminal`           | The thread is in `completed` or `cancelled` state; no more messages can be appended.                    |
| `-32008` | `409`       | `invalid_transition`        | The requested `transition` is not valid from the thread's current state.                                |
| `-32009` | `403`       | `not_authorized_transition` | The caller is not allowed to perform this transition (e.g. a sender trying to accept their own thread). |
| `-32010` | `409`       | `state_changed`             | Optimistic concurrency conflict — another caller transitioned the thread first.                         |
| `-32011` | `409`       | `duplicate_idempotency_key` | The same `client_idempotency_key` was submitted with a different payload.                               |
| `-32012` | `400`       | `invalid_intent_payload`    | `proposed_action` is non-null when `intent` is not `propose_action`, or null when it is (v0.1.5).       |
| `-32013` | `403`       | `teammate_blocked`          | The sender has been blocked by the recipient via `agentrelay block`.                                    |
| `-32099` | `500`       | `internal`                  | Catchall internal error. Details are redacted. Check `request_id` in the relay logs.                    |

## Error handling patterns

### Retrying on server errors

On `500` (`internal`, code `-32099`), retry with exponential backoff. The error is almost always transient (a brief DB hiccup, a restart). Start with a 1-second delay and cap at 30 seconds:

```
1s → 2s → 4s → 8s → 16s → 30s (max)
```

Do not retry on `4xx` errors — they indicate a problem with the request itself, not the server.

### Handling rate limits

On `429` (`rate_limited`, code `-32003`), read the `Retry-After` response header. It contains the number of seconds to wait before retrying. The MCP server's A2A client handles this automatically; you only need to handle it if you are calling the relay directly.

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 14
Content-Type: application/json

{"code": "rate_limited", "message": "Rate limit exceeded", "request_id": "..."}
```

### Handling concurrency conflicts

On `-32010` (`state_changed`), another caller transitioned the thread between your read and your write. Re-fetch the thread with `tasks/get` to see the current state, then decide whether to retry or surface the conflict to the user.

### Idempotency key collisions

On `-32011` (`duplicate_idempotency_key`), the same key was used with a different message body. This means the retry is not truly identical to the original request. Generate a new idempotency key and retry with the correct payload.

<Tip>
  Always include a `client_idempotency_key` in `metadata` when calling `message/send`. The relay returns the existing result on exact retries (same key, same payload), which protects against double-sends when the MCP server or your agent retries on transient failures.
</Tip>
