Documentation

Responses

The common envelope, pagination, headers and language.

Every /api/v1 response — success and error, collection and single resource — has the same shape. You write one parser and it serves all fifty operations.

JSON
{
  "status": "success",
  "message": "Operación exitosa",
  "message_code": "api_v1_ok",
  "data": { "…": "…" },
  "meta": { "request_id": "9f1c0a3e-7b2d-4f18-9a55-2c6e0d1b4a77" }
}
FieldWhat it is
statussuccess or error.
messageHuman-readable text, translated per Accept-Language. For people, not for code.
message_codeStable identifier in lower_snake_case. This is what you branch on.
dataAn object for a single resource, a list for a collection. Never null: when there is nothing, it is [].
metaAlways request_id; on collections, also page, size and total.

Heads up

Do not branch on message: it is interface text, it changes with the language and can be reworded without notice. message_code is contract.

Pagination

Collections paginate with page (starting at 1) and size (default 50, max 200). The total is in meta.total, so you know how many pages there are before walking them.

JSON
{
  "status": "success",
  "message": "Operación exitosa",
  "message_code": "api_v1_ok",
  "data": [ { "id": 31, "name": "Procedimientos de soporte" } ],
  "meta": {
    "request_id": "9f1c0a3e-7b2d-4f18-9a55-2c6e0d1b4a77",
    "page": 1,
    "size": 50,
    "total": 128
  }
}
def all_pages(session, url, token, **params):
    page = 1
    while True:
        payload = session.get(
            url,
            headers={"Authorization": f"Bearer {token}"},
            params={**params, "page": page, "size": 200},
            timeout=30,
        ).json()

        yield from payload["data"]

        meta = payload["meta"]
        if meta["page"] * meta["size"] >= meta["total"]:
            return
        page += 1

Note

page and size are accepted across the API, but they only matter on collections. Sending them on a POST breaks nothing; it also does nothing.

Analytics are not paginated

The eight `/analytics` endpoints and GET /catalog/countries return complete series, not collections: their meta carries only request_id, with no page, size or total, and pagination parameters are ignored. The iterator above does not apply there — it would cut a 365-day series at the first page —: what bounds an analytic is its date range.

Response headers

HeaderWhat it carries
X-Request-IdThe same value as meta.request_id. Keep it in your logs: it is what to quote so we can trace a request.
X-RateLimit-LimitThe credential's requests per minute.
X-RateLimit-RemainingHow many are left in the window.
X-RateLimit-ResetEpoch at which the window resets.
Retry-AfterOnly on a 429: seconds to wait.
Idempotent-Replayedtrue when the response is the replay of a previous turn.
WWW-AuthenticateOn 401 and 403: error="invalid_token" or error="insufficient_scope", scope="…".

Language

The message field is translated using the Accept-Language header. `es` (default) and `en` are supported, with q-values and subtags: en-US resolves to en, and es-AR;q=0.9, en;q=0.8 to es. Anything else falls back to Spanish.

HTTP
GET /api/v1/chats HTTP/1.1
Authorization: Bearer …
Accept-Language: en-US,en;q=0.9

Note

message_code does not change with the language: it is the same identifier in both.

Request sizes

LimitValueWhen exceeded
/api/v1 JSON body256 KB413 payload_too_large
/api/oauth/* form8 KB413 invalid_request
A turn's content64,000 characters422 api_v1_content_too_long
A turn's metadata2048 bytes422 api_v1_metadata_too_large
Attachments per type / total20 / 50422 api_v1_too_many_attachments

Note

The body cap is enforced at the transport layer, over what is actually received — not over the declared Content-Length. A lying header or a giant chunked upload is cut off just the same.

Unknown fields

Bodies are strict: sending a field the API does not accept returns 422 instead of being ignored. This is deliberate — a silently ignored field is a change you believed had been applied and never was.

JSON
{
  "status": "error",
  "message": "El request no cumple el contrato.",
  "message_code": "api_v1_validation_error",
  "data": {
    "errors": [
      {
        "field": "body.anonymize",
        "message": "Extra inputs are not permitted",
        "type": "extra_forbidden"
      }
    ]
  },
  "meta": { "request_id": "…" }
}

Note

Validation detail never includes the value you sent: user content can live there.