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"
  }
}
  • code es el contrato: tu programa puede confiar en él; nunca se reformula.
  • message es un mensaje en inglés para desarrolladores y puede cambiar. No se lo muestres al hostelero.
  • request_id está también en la cabecera X-Request-Id; con él encuentras la solicitud en el registro.
  • param indica el campo erróneo, aunque esté anidado en el cuerpo: products[3].price.

Todos los códigos

CódigoHTTPSignificadoQué hace tu programa
invalid_request400Un 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.
unauthorized401No 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_inactive403La 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_scope403A 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_required403El 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_found404El 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_allowed405Este método no existe en esta ruta.Comparar el método y la ruta con la referencia.
conflict409Los 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_taken409Tu 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_progress409Una primera solicitud con la misma Idempotency-Key todavía está en curso.Esperar un momento y repetir con la misma clave.
invalid_transition409Este 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.
slot_unavailable409La 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_used409El 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_large413El 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_reused422La 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_exceeded422Se 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_limited429Más de 120 solicitudes por minuto para esta conexión.Esperar Retry-After segundos y continuar. Repartir las consultas en el tiempo.
server_error500Algo 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
}

401 y una conexión perdida

Ante un 401, obtén un nuevo token de acceso con el token de actualización y repite la solicitud una vez. Si eso también falla con invalid_grant, la conexión ya no existe: el propietario o tu app la ha desconectado, el token de actualización ha caducado o se ha revocado, o el negocio ya no está activo. Muéstralo en tu software y ofrece «Volver a conectar»; aquí no sirve de nada repetir.

Si, en cambio, el endpoint de token responde con unauthorized_client, tu app está bloqueada, pero sus conexiones se mantienen. Conserva los tokens, muestra «bloqueada temporalmente» y vuelve a intentarlo más tarde.

Cuándo repetir

Caso¿Repetir?
Error de red, tiempo de espera agotadoSí, con la misma Idempotency-Key.
429 rate_limitedSí, tras Retry-After segundos.
500 server_error, 502, 503, 504Sí, con pausas cada vez mayores.
409 idempotency_in_progress, 409 conflictSí, tras una pausa breve (Retry-After), sin cambios.
401 unauthorizedUna vez, tras renovar el token.
400, 403, 404, 413, 422, los demás 409No, 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.