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"
}
}codeest le contrat : votre programme peut s'y fier, il n'est jamais reformulé.messageest une indication en anglais destinée aux développeurs et peut changer. Ne l'affichez pas au restaurateur.request_idfigure aussi dans l'en-têteX-Request-Idet permet de retrouver la requête dans le journal.paramindique le champ fautif, même profondément imbriqué dans le corps :products[3].price.
Tous les codes
| Code | HTTP | Signification | Ce que fait votre programme |
|---|---|---|---|
invalid_request | 400 | Un 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. |
| 401 | Aucun 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_inactive | 403 | L'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_scope | 403 | Il 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_required | 403 | L'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_found | 404 | L'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_allowed | 405 | Cette méthode n'existe pas sur ce chemin. | Comparer la méthode et le chemin avec la référence. |
conflict | 409 | Les 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_taken | 409 | Cet 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_progress | 409 | Une 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_transition | 409 | Ce 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. |
| 409 | L'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_used | 409 | L'é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_large | 413 | Le 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_reused | 422 | La 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_exceeded | 422 | Une 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_limited | 429 | Plus de 120 requêtes par minute pour cette connexion. | Attendre Retry-After secondes, puis continuer. Répartir les requêtes dans le temps. |
server_error | 500 | Un 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
}Quand réessayer
| Cas | Réessayer ? |
|---|---|
| Erreur réseau, délai dépassé | Oui, avec la même Idempotency-Key. |
429 rate_limited | Oui, après Retry-After secondes. |
500 server_error, 502, 503, 504 | Oui, en espaçant de plus en plus les tentatives. |
409 idempotency_in_progress, 409 conflict | Oui, après une courte pause (Retry-After), sans modification. |
401 unauthorized | Une fois, après avoir rafraîchi le jeton. |
| 400, 403, 404, 413, 422, autres 409 | Non, 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.