---
title: "Responses and errors"
description: "The response envelope, the error object and its codes, request ids and size limits."
---

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

# Responses and errors

Every operation answers JSON in the same envelope, whether it succeeds or fails, so one piece of code can read any answer. The one exception is [text answers for AI agents](#text-answers-for-ai-agents), which you ask for.

## The envelope

| Field | On success | On failure |
| --- | --- | --- |
| `success` | `true` | `false` |
| `data` | The result: a record, a [page](/en/developers/concepts/pagination/), or what the operation declares. | `null` |
| `error` | `null` | The [error object](#the-error-object). |
| `metadata` | `{ "requestId": "…" }` | `{ "requestId": "…" }` |

A success, here from [`account.apiKeys.delete`](/api/account/api-keys/delete/):

```json
{
  "success": true,
  "data": { "id": "exampleClientId0001", "deleted": true },
  "error": null,
  "metadata": { "requestId": "00000000-0000-4000-8000-000000000001" }
}
```

The status code tells success from failure too: successes answer `200`, failures `4xx` or `5xx`. The TypeScript SDK resolves with the whole envelope and throws `ApiError` for any other status; the Rust SDK returns `data`.

## The error object

A failure, here a request without a valid credential:

```json
{
  "success": false,
  "data": null,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "A valid access token is required.",
    "retryable": false,
    "data": {}
  },
  "metadata": { "requestId": "00000000-0000-4000-8000-000000000002" }
}
```

| Field | Meaning |
| --- | --- |
| `code` | The failure code, stable across releases. Branch on it. |
| `message` | What went wrong, for people. It never contains input values. Don't parse it. |
| `retryable` | Whether repeating the same request may succeed. See [Retries](/en/developers/concepts/retries/). |
| `data` | Diagnostics the code declares. Empty for most codes. |

For `INVALID_INPUT`, `data.issues` lists up to 10 problems, each with a `path`, an [RFC 6901](https://www.rfc-editor.org/rfc/rfc6901) JSON Pointer to the refused field (empty for the whole input), and a `message`.

## Common codes

These codes can come from any service. Each operation's page in the API reference lists the codes it answers, by status.

| Status | `code` | `retryable` | Meaning |
| --- | --- | --- | --- |
| `400` | `INVALID_INPUT` | `false` | The request does not match the published schema. |
| `401` | `UNAUTHORIZED` | `false` | A valid access token is required. See [401 or 403](/en/developers/authentication/#401-or-403). |
| `403` | `FORBIDDEN` | `false` | The token does not authorize this operation. |
| `404` | `NOT_FOUND` | `false` | Nothing matches the path. |
| `405` | `HTTP_METHOD_NOT_ALLOWED` | `false` | The HTTP method is not supported for this resource. |
| `413` | `REQUEST_TOO_LARGE` | `false` | The request exceeds the allowed size. See [Size limits](#size-limits). |
| `415` | `UNSUPPORTED_MEDIA_TYPE` | `false` | The request content type is not supported. Send JSON bodies as `application/json`. |
| `429` | `RATE_LIMITED` | `true` | The request rate limit was exceeded. Wait the seconds in `Retry-After`. |
| `500` | `INTERNAL_ERROR` | `false` | The service could not complete the operation. |
| `502` | `OUTCOME_UNKNOWN` | `false` | The operation may have been applied. Check its result before repeating it. |
| `503` | `UPSTREAM_UNAVAILABLE` | `true` | The upstream service could not complete the request. |
| `504` | `UPSTREAM_TIMEOUT` | `true` | The upstream service did not respond before its deadline. |

Services add their own codes for their own rules, such as `API_KEY_NOT_FOUND` (`404`) in Account or `VERSION_CONFLICT` (`409`) in Raíz.

## Request ids

Every response carries an `X-Request-Id` header, and the envelope repeats it as `metadata.requestId`. Log it with every failure, and include it when you report a problem.

Responses to `429`, and to some other failures as their reference shows, also carry `Retry-After`: the seconds to wait before another attempt.

## Size limits

Each operation accepts a request and returns a response of up to a maximum size, which its page in the API reference shows under **Limits**. A larger body is refused with `413 REQUEST_TOO_LARGE`; on [MCP](/en/developers/mcp/), the limit bounds the tool call's input.

Most operations accept a body of up to 32 KiB. Operations that carry files accept more, such as [`raiz.knowledge.create`](/api/raiz/knowledge/create/) (12 MiB) and [`raiz.contacts.import`](/api/raiz/contacts/import/) (16 MiB). Responses range from 256 KiB, as for [`account.organization.get`](/api/account/organization/get/), to 16 MiB for [`account.invoiceDocuments.get`](/api/account/invoice-documents/get/). The Rust SDK treats a response larger than its operation's limit as invalid.

In the API's OpenAPI document (`https://api.getarbol.com/v1/<service>/openapi.json`), each operation carries its limits in bytes, as `x-max-body-bytes` and `x-max-output-bytes`.

## Text answers for AI agents

Some integration operations, such as SURA's, Nueva EPS's and ADRES's, take an optional `agentMode`. With `agentMode: true`, they answer concise Spanish text meant for an AI agent, with the same authorization. Depending on the operation, the text comes as `text/plain` instead of JSON, or inside the envelope, in `data.text`; each operation's page in the reference says which. Leave it out to get the structured result.

Source: https://docs.getarbol.com/en/developers/concepts/responses-and-errors/index.mdx
