En esta sección
Guías / Errores, idempotencia y paginación

Errores, idempotencia y paginación

Errores tipados, reintentos seguros con idempotencia, y cómo paginar.

Actualizada el Ver .md

Tres cosas que hacen robusta cualquier integración: entender los errores, reintentar sin duplicar, y recorrer listas grandes.

La forma de una respuesta

Toda respuesta correcta de la API viaja dentro de un sobre. No llega el recurso pelado: llega envuelto, y el recurso está en data.

{
  "success": true,
  "data": { "object": "run", "id": "run_01HXYZ" }
}

Un error usa el mismo sobre con el signo cambiado, y por eso success es el primer campo que conviene mirar:

{
  "success": false,
  "error": { "type": "invalid_request_error", "code": "missing_scope", "message": "…" },
  "request_id": "req_01J8XYZ"
}

Con los SDK oficiales no tienes que pensar en esto: te entregan el contenido de data y convierten el sobre de error en una excepción. El sobre te importa si llamas con curl, si escribes tu propio cliente, o si generas uno del contrato —en cuyo caso ya viene declarado.

Una excepción que conviene conocer: las listas paginadas ponen pagination afuera de data, como hermana y no como hija.

{
  "success": true,
  "data": [ { "id": 9812 }, { "id": 9813 } ],
  "pagination": { "page": 1, "limit": 50, "total": 128, "pages": 3 }
}

Errores tipados

Todos los errores de la API traen un cuerpo con type, code, message y un request_id (envíalo al soporte si algo falla). El SDK los mapea a clases:

import { MetRateLimitError, MetAuthError, MetInvalidRequestError } from '@meteor.ia/sdk';

try {
  await met.runs.create({ input: 'x' });
} catch (err) {
  if (err instanceof MetRateLimitError) {
    console.log(`Reintentar en ${err.retryAfter}s`);
  } else if (err instanceof MetAuthError) {
    console.log('Key inválida o revocada');
  } else if (err instanceof MetInvalidRequestError) {
    console.log(err.code, err.message);
  } else throw err;
}
from meteor_ia import MetRateLimitError, MetAuthError, MetInvalidRequestError

try:
    met.runs.create("x")
except MetRateLimitError as e:
    print(f"Reintentar en {e.retry_after}s")
except MetAuthError:
    print("Key inválida o revocada")
except MetInvalidRequestError as e:
    print(e.code, e.message)

La taxonomía es un conjunto cerrado:

typeClaseHTTP
authentication_errorMetAuthError401
invalid_request_errorMetInvalidRequestError400
rate_limit_errorMetRateLimitError429
agent_errorMetAgentError5xx del Met
api_errorMetApiErrorotros

Cuando algo falla: mira tus propias requests

Cada error trae un request_id, y ese id es buscable. No tienes que adivinar qué pasó ni abrir un ticket para saberlo.

try {
  await met.contacts.create({ name: 'Ada' });
} catch (err) {
  console.error(err.requestId);   // req_01J8… ← guarda esto
}
try:
    met.contacts.create(name="Ada")
except Exception as e:
    print(e.request_id)   # req_01J8… ← guarda esto
curl -i -X POST https://api.met.meteor.com.co/api/v1/contacts \
  -H "Authorization: Bearer $MET_API_KEY" \
  -H "Content-Type: application/json" -d '{}'
# El id viene en el cuerpo y en el header X-Request-Id

Con ese id, entra a tu Workbench en Ajustes → Desarrolladores → Actividad y tienes:

pestañaqué te dice
Registroscada request de tu workspace, filtrable por key, por clase de status (2xx/4xx/5xx) y por request_id. Cada fila se expande y muestra los bodies — los de error incluyen el detalle que causó el 400
Resumenvolumen, distribución de errores y latencia p95, por 24 h / 7 d / 30 d
Saluderrores recientes agrupados por código, más las alertas de cuota por key (80% / 100%)
Webhooksentregas salientes y el log de intentos por endpoint

Es el camino corto: pegas el request_id en Registros y ves exactamente qué mandaste y qué contestamos. Si igual necesitas escribirnos, mándanos ese id — es lo primero que vamos a pedir.

El Workbench se sirve con tu sesión del panel, nunca por API key, y solo muestra el tráfico de tu propio workspace.

Reintentos e idempotencia

El SDK reintenta solo los 429 y 5xx con backoff exponencial (respeta Retry-After). No tienes que reimplementarlo.

Para que reintentar un POST no duplique (por ejemplo, crear dos runs por un corte de red), el SDK manda una Idempotency-Key automática en cada POST con efectos. Si el request se reenvía con la misma key, la API devuelve el mismo resultado sin volver a ejecutar.

// Ambas llamadas comparten idempotencia automática; un retry no crea dos runs.
await met.runs.create({ input: 'x' });

Con curl, manda tú el header:

-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000"

Paginación

Hay dos formas de paginar en la API, y conviene saber cuál te toca — no porque cambie mucho el código, sino porque usar la equivocada no da error.

Por cursor

Runs, contactos e ítems de una colección devuelven { data, has_more } con un cursor opaco. El SDK lo recorre por ti:

for await (const run of met.runs.iterate()) {
  // ...cada run, todas las páginas
}
for run in met.runs.iterate():
    ...

Si prefieres controlar el cursor a mano, list() acepta limit y starting_after:

const page = await met.runs.list({ limit: 20 });
if (page.has_more) {
  const next = await met.runs.list({ limit: 20, starting_after: page.data.at(-1).id });
}

Es el modo estable: aunque entren registros nuevos mientras recorres, no se te duplica ni se te salta nada.

Por desplazamiento

Facturación, Mediateca, eventos, entregas de webhook y la búsqueda de ítems paginan con limit + offset (la búsqueda, con limit + page). Cada uno tiene su iterador:

for await (const e of met.billing.iterateExecutions()) { /* ... */ }
for await (const a of met.assets.iterate({ mime_prefix: 'image/' })) { /* ... */ }
for await (const it of met.items.iterateSearch('factura')) { /* ... */ }
for e in met.billing.iterate_executions():
    ...
for a in met.assets.iterate(mime_prefix="image/"):
    ...

Lo que hay que saber: mandarle starting_after a uno de estos endpoints no falla. El parámetro se ignora, responde 200 y devuelves la primera página una y otra vez — un bucle infinito que parece funcionar. Si escribes la paginación a mano, revisa en la referencia qué parámetros acepta el endpoint.

Y el desplazamiento no es estable: si algo entra o sale del conjunto mientras recorres, una fila puede aparecer dos veces o ninguna. Es una limitación del endpoint. Para un barrido exacto sobre datos que se mueven, el que sirve es el de cursor.