Errores, idempotencia y paginación
Errores tipados, reintentos seguros con idempotencia, y cómo paginar.
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:
type | Clase | HTTP |
|---|---|---|
authentication_error | MetAuthError | 401 |
invalid_request_error | MetInvalidRequestError | 400 |
rate_limit_error | MetRateLimitError | 429 |
agent_error | MetAgentError | 5xx del Met |
api_error | MetApiError | otros |
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 estocurl -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-IdCon ese id, entra a tu Workbench en Ajustes → Desarrolladores → Actividad y tienes:
| pestaña | qué te dice |
|---|---|
| Registros | cada 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 |
| Resumen | volumen, distribución de errores y latencia p95, por 24 h / 7 d / 30 d |
| Salud | errores recientes agrupados por código, más las alertas de cuota por key (80% / 100%) |
| Webhooks | entregas 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.