Envoltura de respuesta
Cada endpoint v6 de Hablame, exitoso o fallido, devuelve las
mismas tres llaves de nivel superior: success,
exactamente una de data / error,
y meta. Una sola rama en tu cliente. Sin adivinar la forma.
Respuesta exitosa
{ "success": true, "data": { "pong": true, "apiVersion": "v6", "account": { "id": 10000003 } }, "meta": { "requestId": "b9b1704baffab21150213c02fd853975", "timestamp": "2026-05-22T19:43:59+00:00", "responseTimeMs": 4.24 } }
data es el payload real del endpoint. La forma varía por endpoint: ver la referencia para el schema de cada uno.
Respuesta de error
{ "success": false, "error": { "code": "AUTH_REQUIRED", "legacyCode": 40002, "type": "https://developers.hablame.co/docs/v6/errors/auth-required", "message": "Authentication is required. Send your API key as `Authorization: Bearer`." , "details": [] }, "meta": { "requestId": "a883f4341b91353de262ce9812d270aa", "timestamp": "2026-05-22T19:00:00+00:00", "responseTimeMs": 0.6 } }
Campos del error
| Campo | Obligatorio | Notas |
|---|---|---|
code |
Sí | Identificador estable UPPER_SNAKE_CASE. Ramifica tu cliente sobre este. No cambia entre versiones ni cuando se reescribe el mensaje. |
legacyCode |
No | Código numérico del catálogo v5. Presente solo cuando hay un puente: los códigos introducidos en v6 lo omiten. Usado por clientes que aún mapean desde v5. |
type |
Sí | URL al catálogo de esta sección. Abre directo en la sección correcta de /docs/es/v6/errors. |
message |
Sí | Texto humano, en inglés. Seguro para mostrar a ingenieros. No para usuarios finales: traduce o generaliza antes de mostrarlo. |
details |
Sí (puede ir vacío) | Array de objetos de contexto estructurado. La mayoría de códigos lo dejan vacío; errores de validación lo llenan con issues por campo. |
El bloque meta
Presente en toda respuesta, exitosa o error.
| Campo | Notas |
|---|---|
requestId |
Trace id del lado servidor asignado por nginx. Cítalo en tickets de soporte: le permite a ops encontrar tu request exacto en los logs. |
timestamp |
ISO-8601 con offset de timezone. El momento server-side en que se construyó el body de la respuesta. |
responseTimeMs |
Tiempo de construcción del lado servidor en milisegundos. Excluye red: mide solo lo que gastamos procesando. |
warnings |
Opcional. Array de objetos { code, message }. Aparece solo cuando el request fue exitoso pero produjo warnings no-fatales (ej. avisos de deprecación). |
Ramificar tu cliente
El patrón es el mismo en cualquier lenguaje: chequea success primero; nunca inspecciones HTTP status solo.
TypeScript
type Envelope<T> =
| { success: true; data: T; meta: Meta }
| { success: false; error: ApiError; meta: Meta };
const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } });
const body = await res.json() as Envelope<PingData>;
if (body.success) {
console.log(body.data.pong); // narrowed to PingData
} else {
// ramifica sobre el code estable, NO sobre el HTTP status
if (body.error.code === 'RATE_TPS_EXCEEDED') {
const wait = Number(res.headers.get('Retry-After') ?? 5);
await sleep(wait * 1000);
return retry();
}
throw new Error(`${body.error.code}: ${body.error.message}`);
}
Python
import time, httpx
r = httpx.get(url, headers={"Authorization": f"Bearer {key}"})
body = r.json()
if body["success"]:
print(body["data"]["pong"])
else:
code = body["error"]["code"]
if code == "RATE_TPS_EXCEEDED":
time.sleep(int(r.headers.get("Retry-After", "5")))
return retry()
raise RuntimeError(f"{code}: {body['error']['message']}")
HTTP status vs error.code
Sirven a propósitos distintos:
- HTTP status le dice a routers, CDNs y middleware si la respuesta es OK (2xx), recuperable (4xx) o terminal (5xx). Úsalo para decisiones a nivel transporte (retry vs fallar).
error.codele dice a tu aplicación cuál falla ocurrió. Úsalo para decisiones a nivel producto (re-autenticar, refrescar, mostrar UI específica).
Dos códigos distintos pueden compartir el mismo HTTP status; por ejemplo, tanto AUTH_REQUIRED como AUTH_COST_CENTER_DISABLED son 401. El code te dice si el fix es "añadir el header" o "pídele al admin que reactive el centro de costo".
No parsees error.message en código. La redacción puede cambiar entre releases sin romper el contrato; el code es el contrato.
Invariantes en los que puedes confiar
- La forma de la envoltura nunca cambia entre versiones del mismo endpoint. Se añaden campos nuevos a
dataometa(aditivo), los existentes mantienen su tipo. successy la presencia/ausencia dedatavserrorson mutuamente consistentes: uno está siempre, el otro siempre ausente.meta.requestIdes único por request y seguro de loguear.- Los valores de
error.codeestán listados en el catálogo de errores y nunca se reutilizan ni renombran.