Todos los errores de la API de Fenicia siguen un formato JSON consistente. Esta página es la referencia canónica para cada código de error que puede devolver la API de Pedidos.
{
"code": "INVALID_API_KEY",
"message": "API key inválida o expirada"
}Algunos errores incluyen campos adicionales (por ejemplo retryAfter en respuestas de rate limit, o details en errores de validación).
{
"code": "validation:invalid_format",
"message": "El campo 'customerInfo.email' tiene un formato inválido",
"details": { "field": "customerInfo.email", "expected": "email" }
}| Código | Descripción |
|---|---|
MISSING_AUTHORIZATION | No se envió el header Authorization |
INVALID_AUTHORIZATION_FORMAT | El header no sigue el formato Bearer <key> |
INVALID_API_KEY | La API key no existe, expiró o fue revocada |
{ "code": "INVALID_API_KEY", "message": "API key inválida o expirada" }| Código | Descripción |
|---|---|
INSUFFICIENT_PERMISSIONS | La API key no tiene el scope requerido para este endpoint |
account/billing_restricted | El tenant está suspendido por problemas de facturación |
{
"code": "INSUFFICIENT_PERMISSIONS",
"message": "La API key no tiene el scope requerido: orders:create"
}| Código | Descripción |
|---|---|
validation:missing_field | Falta un campo obligatorio |
validation:invalid_format | El campo tiene un formato incorrecto (email, fecha, UUID, etc.) |
validation:invalid_value | Valor no permitido (enum inválido, fuera de rango) |
{
"code": "validation:missing_field",
"message": "El campo 'customerInfo.email' es obligatorio",
"details": { "field": "customerInfo.email" }
}| Código | Descripción |
|---|---|
order_not_found | No existe un pedido con el ID dado para este tenant |
return_not_found | No existe una devolución con el ID dado |
attachment_not_found | No existe un adjunto con el ID dado |
{ "code": "order_not_found", "message": "Pedido ord_123 no encontrado" }| Código | Descripción |
|---|---|
invalid_transition | No se puede transicionar al estado destino desde el estado actual |
order_already_cancelled | El pedido ya está cancelado |
insufficient_inventory | Inventario insuficiente para cumplir el pedido |
{
"code": "invalid_transition",
"message": "No se puede transicionar de 'cancelled' a 'fulfilled'"
}| Código | Descripción |
|---|---|
RATE_LIMIT_EXCEEDED | Se excedió la tasa de peticiones. Inspecciona retryAfter (segundos) para saber cuándo reintentar |
{
"code": "RATE_LIMIT_EXCEEDED",
"message": "Demasiadas peticiones",
"retryAfter": 45
}Advertencia
Respeta siempre retryAfter. Reintentar antes mantendrá tu acceso bloqueado y puede disparar cooldowns más largos.
| Código | Estado | Descripción |
|---|---|---|
INTERNAL_ERROR | 500 | Error genérico del servidor. Transitorio, seguro reintentar con backoff |
EXTERNAL_SERVICE_UNAVAILABLE | 503 | El canal o proveedor de pagos externo no está disponible |
{
"code": "EXTERNAL_SERVICE_UNAVAILABLE",
"message": "La API de Shopify no está disponible"
}Para errores transitorios (500, 503, timeouts de red), reintenta con backoff exponencial — duplicando el retraso en cada intento.
async function requestWithRetry(url, options, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const res = await fetch(url, options);
if (res.status === 429) {
const { retryAfter = 1 } = await res.json();
await sleep(retryAfter * 1000);
continue;
}
if (res.status >= 500) {
const delay = Math.min(1000 * 2 ** attempt, 30_000);
await sleep(delay);
continue;
}
return res;
}
throw new Error("Máximo de reintentos alcanzado");
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));| Estado | ¿Reintentar? | Estrategia |
|---|---|---|
400 validación | No | Corregir la petición, propagar el error al llamador |
401 auth | No | Rotar la key o re-autenticar |
403 permisos | No | Solicitar un upgrade de scope |
404 no encontrado | No | El recurso no existe |
409 lógica de negocio | A veces | Solo si puedes mutar el estado para resolver el conflicto |
429 rate limit | Sí | Esperar retryAfter y reintentar |
500 interno | Sí | Backoff exponencial, hasta ~5 intentos |
503 externo caído | Sí | Backoff más largo — la dependencia se está recuperando |
Los endpoints mutables (POST, PUT, DELETE) aceptan un header Idempotency-Key. Úsalo al reintentar para evitar crear recursos duplicados:
curl -X POST https://api.fenicia.io/orders \
-H "Authorization: Bearer fn_live_tu_api_key" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{...}'Tip
Genera una clave de idempotencia nueva por operación lógica, pero reutiliza la misma clave en los reintentos de esa operación. Guárdala junto a la operación hasta recibir una respuesta exitosa.
code, no el messageLos mensajes de error pueden estar traducidos o cambiar con el tiempo. Los códigos son estables — ramifica tu lógica por code en tu integración, no por el texto del mensaje.
// Correcto
if (error.code === "insufficient_inventory") {
await notifyMerchant(order);
}
// Frágil
if (error.message.includes("stock")) { ... }support@fenicia.io