Documentación
Respuestas
El envelope común, la paginación, los headers y el idioma.
Todas las respuestas de /api/v1 —éxito y error, colección y recurso— tienen la misma forma. Escribís un parser una vez y sirve para las cincuenta operaciones.
{
"status": "success",
"message": "Operación exitosa",
"message_code": "api_v1_ok",
"data": { "…": "…" },
"meta": { "request_id": "9f1c0a3e-7b2d-4f18-9a55-2c6e0d1b4a77" }
}| Campo | Qué es |
|---|---|
status | success o error. |
message | Texto legible, traducido según Accept-Language. Para personas, no para código. |
message_code | Identificador estable en lower_snake_case. Esto es lo que hay que ramificar. |
data | Un objeto para un recurso, una lista para una colección. Nunca null: si no hay nada, es []. |
meta | Siempre request_id; en colecciones, además page, size y total. |
Atención
No ramifiques sobre message: es texto de interfaz, cambia con el idioma y puede reescribirse sin previo aviso. message_code es contrato.
Paginación
Las colecciones se paginan con page (empieza en 1) y size (por defecto 50, máximo 200). El total va en meta.total, así que sabés cuántas páginas hay antes de recorrerlas.
{
"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 += 1Nota
page y size se aceptan en toda la API, pero solo tienen efecto en las colecciones. Mandarlos en un POST no rompe nada; tampoco hace nada.
Las analíticas no se paginan
Los ocho endpoints de `/analytics` y GET /catalog/countries devuelven series completas, no colecciones: su meta trae solo request_id, sin page, size ni total, y los parámetros de paginación se ignoran. El iterador de arriba no se aplica ahí —cortaría una serie de 365 días en la primera página—: lo que acota una analítica es su rango de fechas.
Headers de respuesta
| Header | Qué trae |
|---|---|
X-Request-Id | El mismo valor que meta.request_id. Guardalo en tus logs: es lo que hay que citar para que podamos rastrear un request. |
X-RateLimit-Limit | Requests por minuto de la credencial. |
X-RateLimit-Remaining | Cuántos quedan en la ventana. |
X-RateLimit-Reset | Epoch en el que la ventana se reinicia. |
Retry-After | Solo en un 429: segundos a esperar. |
Idempotent-Replayed | true cuando la respuesta es la reentrega de un turno anterior. |
WWW-Authenticate | En 401 y 403: error="invalid_token" o error="insufficient_scope", scope="…". |
Idioma
El campo message se traduce con el header Accept-Language. Se admiten `es` (por defecto) e `en`, con q-values y subtags: en-US resuelve a en, y es-AR;q=0.9, en;q=0.8 a es. Cualquier otro valor cae a español.
GET /api/v1/chats HTTP/1.1
Authorization: Bearer …
Accept-Language: en-US,en;q=0.9Nota
message_code no cambia con el idioma: es el mismo identificador en los dos.
Tamaños del request
| Límite | Valor | Al superarlo |
|---|---|---|
Cuerpo JSON de /api/v1 | 256 KB | 413 payload_too_large |
Formulario de /api/oauth/* | 8 KB | 413 invalid_request |
content de un turno | 64.000 caracteres | 422 api_v1_content_too_long |
metadata de un turno | 2048 bytes | 422 api_v1_metadata_too_large |
| Adjuntos por tipo / totales | 20 / 50 | 422 api_v1_too_many_attachments |
Nota
El tope de cuerpo se aplica en el transporte, sobre lo realmente recibido — no sobre el Content-Length declarado. Un header mentiroso o un chunked gigante se cortan igual.
Campos desconocidos
Los cuerpos son estrictos: mandar un campo que la API no acepta devuelve 422 en vez de ignorarse. Es a propósito — un campo ignorado en silencio es un cambio que creíste que se aplicó y nunca se aplicó.
{
"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": "…" }
}Nota
El detalle de validación nunca incluye el valor que mandaste: ahí puede haber contenido del usuario.