API de FlavosoFundamentosErrores
Errores
Todos los errores tienen el mismo formato. Aquí están todos los códigos con su estado HTTP y lo que debe hacer tu programa.
El formato
Un error es siempre un objeto error con code, message y request_id; en invalid_request, a menudo también param:
{
"error": {
"code": "invalid_request",
"message": "price must be an integer amount in cents.",
"request_id": "req_4f0c2a9d1e7b3c5a8f6d2e10",
"param": "products[3].price"
}
}codees el contrato: tu programa puede confiar en él; nunca se reformula.messagees un mensaje en inglés para desarrolladores y puede cambiar. No se lo muestres al hostelero.request_idestá también en la cabeceraX-Request-Id; con él encuentras la solicitud en el registro.paramindica el campo erróneo, aunque esté anidado en el cuerpo:products[3].price.
Todos los códigos
| Código | HTTP | Significado | Qué hace tu programa |
|---|---|---|---|
invalid_request | 400 | Un parámetro o el cuerpo no es correcto: falta un campo obligatorio, el tipo es incorrecto, el JSON no es válido o la solicitud contiene un campo o un parámetro de consulta que la operación no conoce. param indica el campo, si lo hay. | No repetir sin cambios. Corregir la solicitud con ayuda de param y message. |
| 401 | No hay token, el token ha caducado o se ha revocado, la conexión ya no existe o la app está bloqueada. | Obtener un nuevo token de acceso con el token de actualización y repetir una vez. Si eso falla con invalid_grant, la conexión ya no existe; con unauthorized_client, la app está bloqueada: conservar los tokens y volver a intentarlo más tarde. | |
account_inactive | 403 | La suscripción del negocio no está activa. GET /me y DELETE /connection siguen funcionando. | No repetir. Volver a intentarlo más tarde o avisar al negocio. |
insufficient_scope | 403 | A la conexión le falta el permiso para esta operación. WWW-Authenticate lo indica. | Pedir al propietario que vuelva a conectar tu app con este permiso. Hasta entonces, desactivar la función. |
plan_required | 403 | El plan del negocio no incluye la función, por ejemplo las reservas en el plan Starter. | Desactivar la función para este negocio y mostrar el motivo al usuario. |
not_found | 404 | El objeto no existe, o no existe en este negocio. También se devuelve para una ruta desconocida. | Comprobar el identificador y la ruta. Eliminar de tu almacenamiento un objeto borrado. |
method_not_allowed | 405 | Este método no existe en esta ruta. | Comparar el método y la ruta con la referencia. |
conflict | 409 | Los mismos datos se han cambiado en otro sitio en el mismo momento, por ejemplo, el negocio desde el panel, tres veces seguidas. La solicitud en sí es correcta. | Esperar Retry-After segundos y volver a enviar la misma solicitud sin cambios, con la misma Idempotency-Key. |
external_id_taken | 409 | Tu app ya usa este external_id para otro objeto del mismo tipo en este negocio. | Consultar con ?external_id= qué objeto lo lleva y corregir la asignación en tu sistema. |
idempotency_in_progress | 409 | Una primera solicitud con la misma Idempotency-Key todavía está en curso. | Esperar un momento y repetir con la misma clave. |
invalid_transition | 409 | Este cambio de estado no es posible, por ejemplo hacia atrás o desde un estado final. En las reservas, message indica los estados posibles en ese momento. | Volver a leer el objeto: casi siempre ya se ha cambiado en otro sitio. Después, decidir. |
| 409 | La hora no es una hora reservable del día o ya ha pasado, o a esa hora no hay sitio para este número de personas o estas mesas. | Obtener las horas libres con GET /reservations/availability y ofrecer una de ellas. | |
stage_not_used | 409 | El negocio no usa este estado, por ejemplo preparing. GET /store indica los que usa en order_statuses. | Saltar el paso y pasar al siguiente estado. |
payload_too_large | 413 | El cuerpo es más grande de lo que admite la operación, contado en bytes: 64 KB, 2 MB en PUT /menu. | Dividir la solicitud, por ejemplo la carta en varias sincronizaciones con merge. |
idempotency_key_reused | 422 | La misma Idempotency-Key llegó con una solicitud distinta (método, ruta o cuerpo). | Usar una clave nueva para cada operación nueva. Buscar el error en el programa. |
limit_exceeded | 422 | Se ha superado un límite, por ejemplo demasiados platos en una sincronización. | Dividir o acortar la solicitud. Los límites figuran en la guía de cada caso. |
rate_limited | 429 | Más de 120 solicitudes por minuto para esta conexión. | Esperar Retry-After segundos y continuar. Repartir las consultas en el tiempo. |
server_error | 500 | Algo ha fallado en Flavoso. | Repetir con pausas cada vez mayores, en POST con la misma Idempotency-Key. Si persiste, enviar el request_id al soporte. |
Tratar los errores
Un error te dice si tiene sentido repetir. Un 401 significa que debes renovar el token. Un 403, que debes avisar al propietario y no repetir. Un 409 suele significar que el objeto ha cambiado en otro sitio, así que vuelve a leerlo. Cualquier otro 4xx significa que la solicitud no es correcta: no la repitas sin cambios.
export class FlavosoError extends Error {
constructor(status, error) {
super(`${status} ${error.code}: ${error.message}`);
Object.assign(this, { status, code: error.code, param: error.param, requestId: error.request_id });
}
}
export async function flavoso(conn, method, path, { body, idempotencyKey } = {}) {
const res = await fetch(`https://api.flavoso.com/v1${path}`, {
method,
headers: {
Authorization: `Bearer ${await accessTokenFor(conn)}`,
...(body ? { "Content-Type": "application/json" } : {}),
...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const data = await res.json();
if (res.ok) return data;
const err = new FlavosoError(res.status, data.error);
switch (err.code) {
case "unauthorized": // expired or revoked: refresh once, then give up
conn.expiresAt = 0;
break;
case "insufficient_scope": // ask the owner to connect again with this scope
case "plan_required": // the restaurant's plan does not include this
case "account_inactive": // the restaurant's subscription is not active
await flagConnection(conn, err.code);
break;
}
throw err; // log err.requestId: support can find the request with it
}Cuándo repetir
| Caso | ¿Repetir? |
|---|---|
| Error de red, tiempo de espera agotado | Sí, con la misma Idempotency-Key. |
429 rate_limited | Sí, tras Retry-After segundos. |
500 server_error, 502, 503, 504 | Sí, con pausas cada vez mayores. |
409 idempotency_in_progress, 409 conflict | Sí, tras una pausa breve (Retry-After), sin cambios. |
401 unauthorized | Una vez, tras renovar el token. |
| 400, 403, 404, 413, 422, los demás 409 | No, salvo que la modifiques. |
const RETRYABLE = new Set([429, 500, 502, 503, 504]);
// send() makes the SAME request every time, with the same Idempotency-Key.
export async function withRetries(send, attempts = 5) {
for (let attempt = 1; ; attempt++) {
let res = null;
try {
res = await send();
} catch (err) {
if (attempt >= attempts) throw err; // network error: outcome unknown, so retry
}
if (res && (!RETRYABLE.has(res.status) || attempt >= attempts)) return res;
const retryAfter = Number(res?.headers.get("retry-after"));
const backoff = Math.min(30, 2 ** attempt) * (0.5 + Math.random() / 2);
await new Promise((r) => setTimeout(r, (retryAfter > 0 ? retryAfter : backoff) * 1000));
}
}Contactar con el soporte
Escribe a support@flavoso.com e indica el request_id (o X-Request-Id), además de la hora y la operación. Así podremos encontrar la solicitud. El mismo identificador está en tu registro de solicitudes.
¿Algo no ha quedado claro? Escribe a support@flavoso.com e indica el identificador de X-Request-Id. Así podremos encontrar tu solicitud en el registro.