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

200 OK
{
  "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

401 Unauthorized
{
  "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

CampoObligatorioNotas
code 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 URL al catálogo de esta sección. Abre directo en la sección correcta de /docs/es/v6/errors.
message 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.

CampoNotas
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.code le 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".

i

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 data o meta (aditivo), los existentes mantienen su tipo.
  • success y la presencia/ausencia de data vs error son mutuamente consistentes: uno está siempre, el otro siempre ausente.
  • meta.requestId es único por request y seguro de loguear.
  • Los valores de error.code están listados en el catálogo de errores y nunca se reutilizan ni renombran.