API FlavosoFondamentauxErreurs

Erreurs

Chaque erreur a le même format. Voici tous les codes avec leur statut HTTP et ce que votre programme doit faire.

Le format

Une erreur est toujours un objet error avec code, message et request_id, et, pour invalid_request, souvent aussi param :

{
  "error": {
    "code": "invalid_request",
    "message": "price must be an integer amount in cents.",
    "request_id": "req_4f0c2a9d1e7b3c5a8f6d2e10",
    "param": "products[3].price"
  }
}
  • code est le contrat : votre programme peut s'y fier, il n'est jamais reformulé.
  • message est une indication en anglais destinée aux développeurs et peut changer. Ne l'affichez pas au restaurateur.
  • request_id figure aussi dans l'en-tête X-Request-Id et permet de retrouver la requête dans le journal.
  • param indique le champ fautif, même profondément imbriqué dans le corps : products[3].price.

Tous les codes

CodeHTTPSignificationCe que fait votre programme
invalid_request400Un paramètre ou le corps est incorrect : un champ obligatoire manque, un type est faux, le JSON est invalide, ou la requête contient un champ ou un paramètre de requête que l'opération ne connaît pas. param indique le champ, s'il y en a un.Ne pas réessayer sans modification. Corriger la requête à l'aide de param et message.
unauthorized401Aucun jeton, un jeton expiré ou révoqué, la connexion n'existe plus, ou l'application est bloquée.Obtenir un nouveau jeton d'accès avec le jeton de rafraîchissement et réessayer une fois. Si cela échoue avec invalid_grant, la connexion n'existe plus ; avec unauthorized_client, l'application est bloquée : conserver les jetons et réessayer plus tard.
account_inactive403L'abonnement de l'établissement n'est pas actif. GET /me et DELETE /connection fonctionnent quand même.Ne pas réessayer tout de suite. Retenter plus tard ou informer l'établissement.
insufficient_scope403Il manque à la connexion le droit nécessaire pour cette opération. WWW-Authenticate l'indique.Demander au propriétaire de connecter à nouveau votre application avec ce droit. D'ici là, désactiver la fonction.
plan_required403L'offre de l'établissement n'inclut pas la fonction, par exemple les réservations avec l'offre Starter.Désactiver la fonction pour cet établissement et en montrer la raison à l'utilisateur.
not_found404L'objet n'existe pas, ou pas dans cet établissement. Vaut aussi pour un chemin inconnu.Vérifier l'identifiant et le chemin. Retirer un objet supprimé de vos données.
method_not_allowed405Cette méthode n'existe pas sur ce chemin.Comparer la méthode et le chemin avec la référence.
conflict409Les mêmes données ont été modifiées ailleurs au même moment, par exemple par l'établissement dans le tableau de bord, trois fois de suite. La requête elle-même est correcte.Attendre Retry-After secondes et renvoyer la même requête sans modification, avec la même Idempotency-Key.
external_id_taken409Cet external_id appartient déjà, pour votre application dans cet établissement, à un autre objet du même type.Chercher avec ?external_id= quel objet le porte, et corriger la correspondance dans votre système.
idempotency_in_progress409Une première requête avec la même Idempotency-Key est encore en cours.Patienter un instant et réessayer avec la même clé.
invalid_transition409Ce changement de statut n'est pas possible, par exemple un retour en arrière ou depuis un statut final. Pour les réservations, message indique les statuts possibles à ce moment.Relire l'objet : il a en général déjà été modifié ailleurs. Puis décider.
slot_unavailable409L'horaire n'est pas un horaire réservable de la journée ou il est passé, ou à cet horaire il n'y a pas de place pour ce nombre de personnes ou ces tables.Récupérer les créneaux libres avec GET /reservations/availability et en proposer un.
stage_not_used409L'établissement n'utilise pas ce statut, par exemple preparing. GET /store indique ceux qu'il utilise sous order_statuses.Sauter l'étape et passer au statut suivant.
payload_too_large413Le corps est plus grand que ce que l'opération accepte, compté en octets : 64 Ko, 2 Mo pour PUT /menu.Diviser la requête, par exemple la carte en plusieurs synchronisations avec merge.
idempotency_key_reused422La même Idempotency-Key est arrivée avec une autre requête (méthode, chemin ou corps).Utiliser une nouvelle clé pour chaque nouvelle opération. Chercher l'erreur dans votre programme.
limit_exceeded422Une limite est dépassée, par exemple trop de plats dans une synchronisation.Diviser ou raccourcir la requête. Les limites figurent dans le guide correspondant.
rate_limited429Plus de 120 requêtes par minute pour cette connexion.Attendre Retry-After secondes, puis continuer. Répartir les requêtes dans le temps.
server_error500Un problème est survenu du côté de Flavoso.Réessayer en espaçant de plus en plus les tentatives, pour un POST avec la même Idempotency-Key. Si le problème persiste, envoyer le request_id au support.

Traiter les erreurs

Une erreur vous indique si une nouvelle tentative a une chance d'aboutir. 401 signifie : rafraîchir le jeton. 403 signifie : prévenir le propriétaire, ne pas réessayer. 409 signifie le plus souvent : l'objet a été modifié ailleurs, donc le relire. Tout autre 4xx signifie : la requête est incorrecte, donc ne pas la renvoyer telle quelle.

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 et connexion perdue

En cas de 401, obtenez un nouveau jeton d'accès avec le jeton de rafraîchissement et réessayez une fois. Si le rafraîchissement échoue à son tour avec invalid_grant, la connexion n'existe plus : le propriétaire ou votre application l'a déconnectée, le jeton de rafraîchissement a expiré ou a été révoqué, ou l'établissement n'est plus actif. Signalez-le dans votre logiciel et proposez « Reconnecter » ; réessayer ne sert à rien ici.

Si le point de terminaison des jetons répond en revanche unauthorized_client, votre application est bloquée, mais les connexions subsistent. Conservez les jetons, affichez « temporairement bloquée » et réessayez plus tard.

Quand réessayer

CasRéessayer ?
Erreur réseau, délai dépasséOui, avec la même Idempotency-Key.
429 rate_limitedOui, après Retry-After secondes.
500 server_error, 502, 503, 504Oui, en espaçant de plus en plus les tentatives.
409 idempotency_in_progress, 409 conflictOui, après une courte pause (Retry-After), sans modification.
401 unauthorizedUne fois, après avoir rafraîchi le jeton.
400, 403, 404, 413, 422, autres 409Non, pas sans modification.
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));
  }
}

Avec le support

Écrivez à support@flavoso.com en indiquant le request_id (ou X-Request-Id), ainsi que l'heure et l'opération. Nous pourrons ainsi retrouver la requête. Le même identifiant figure dans votre journal des requêtes.

Une question reste ouverte ? Écrivez à support@flavoso.com en indiquant l'identifiant de X-Request-Id. Il nous permet de retrouver votre requête dans le journal.