Errores y límites
La forma única de error, qué hacer en cada estado y cómo tratar el 429
Todo error de la API, en cualquier ruta y en cualquier estado, tiene el mismo cuerpo. Si aprendes a leer uno, los has leído todos.
{
"code": "INSUFFICIENT_CREDITS",
"extra": { "credits_needed": 121, "current_balance": 40 }
}codees un identificador estable, en mayúsculas, de un conjunto cerrado. Es sobre él que decide tuswitch.extrasolo aparece cuando hay algo que hacer: qué faltó, qué campo no pasó, qué ids se rechazaron.- No hay mensaje listo. El servidor no sabe el idioma de quien usa tu producto, y el texto corrido no se puede tratar en código. La frase la escribes tú.
El estado va en minúsculas, el error en mayúsculas. status dice dónde está algo (queued, processing, completed, failed); code y error_code dicen qué salió mal (VIDEO_TOO_LONG, DOWNLOAD_NO_AUDIO).
Qué hacer en cada estado
| Estado | Qué significa | Qué hacer |
|---|---|---|
400 | El cuerpo de la petición está mal | Corrige el payload. Repetirlo igual no sirve |
401 | Clave ausente, revocada o caducada | Revisa la cabecera X-Api-Key |
402 | Créditos insuficientes | Recarga, o lee extra.credits_needed |
403 | El vídeo o la cuenta no está accesible | No hay nada que repetir |
404 | No existe en este workspace | Revisa el id y el workspace |
409 | Existe, pero su estado no acepta la operación | Espera y vuelve a intentar |
410 | Existió y caducó | Vuelve a enviar o a renderizar |
422 | Enlace válido, vídeo que no sirve | Prueba otra fuente |
429 | Límite de peticiones alcanzado | Espera el Retry-After |
5xx | Error nuestro | Reintenta con backoff |
La lista completa de code de cada ruta está en la referencia, respuesta por respuesta.
Tratando errores en tu código
El patrón que lo cubre todo: lee el code y deja que el estado decida si vale la pena reintentar.
const RETRIABLE = new Set([429, 500, 502, 503, 504]);
async function call(path, init = {}, attempt = 1) {
const response = await fetch(`https://api.cut.pro/api/v1${path}`, {
...init,
headers: { "X-Api-Key": process.env.CUTPRO_API_KEY, "Content-Type": "application/json" },
});
if (response.ok) return response.json();
const body = await response.json();
if (RETRIABLE.has(response.status) && attempt <= 5) {
// El servidor dice cuánto esperar en el 429. En los 5xx, backoff exponencial.
const retryAfter = Number(response.headers.get("retry-after"));
const wait = retryAfter > 0 ? retryAfter : 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
return call(path, init, attempt + 1);
}
throw Object.assign(new Error(body.code), { code: body.code, extra: body.extra, status: response.status });
}Y entonces tu producto traduce el code al idioma de quien está mirando:
switch (error.code) {
case "INSUFFICIENT_CREDITS":
show(`Te faltan ${error.extra.credits_needed - error.extra.current_balance} créditos.`);
break;
case "VIDEO_TOO_LONG":
show(`Ese vídeo dura ${error.extra.duration}s y tu plan acepta hasta ${error.extra.max_duration}s.`);
break;
case "LIVE_STREAM":
show("La transmisión sigue en directo. Inténtalo cuando pase a ser vídeo.");
break;
default:
show("No hemos podido procesarlo ahora. Inténtalo de nuevo en un momento.");
}Límites de peticiones
600 peticiones por minuto, por clave de API, en una ventana fija de 60 segundos. Cada respuesta dice dónde estás:
| Cabecera | Qué trae |
|---|---|
X-RateLimit-Limit | El techo (600) |
X-RateLimit-Remaining | Cuántas llamadas quedan en esta ventana |
X-RateLimit-Reset | Unix, en segundos, de cuándo se reinicia la ventana |
Al pasar del techo, la respuesta es 429 con { "code": "RATE_LIMIT_EXCEEDED" } y la cabecera Retry-After en segundos. Respétala: reintentar al instante solo quema la ventana siguiente.
POST /clips/info tiene su propio límite, más estrecho, de 10 por minuto, porque cada llamada va hasta la plataforma de origen a buscar los metadatos. Analiza una vez y guarda el id.
El límite es por clave, no por IP: varios procesos tuyos con la misma clave comparten la misma cuota, y dos claves detrás de la misma IP no se estorban.
¿Cabe el polling?
De sobra. Seguir una tarea cada 10 segundos gasta 6 peticiones por minuto, así que puedes tener decenas de trabajos en marcha. Lo que revienta el límite es un bucle sin espera.