API di FlavosoFondamentiErrori
Errori
Ogni errore ha la stessa forma. Qui trovi tutti i codici con lo stato HTTP e cosa deve fare il tuo programma.
La forma
Un errore è sempre un oggetto error con code, message e request_id, e con invalid_request spesso anche param:
{
"error": {
"code": "invalid_request",
"message": "price must be an integer amount in cents.",
"request_id": "req_4f0c2a9d1e7b3c5a8f6d2e10",
"param": "products[3].price"
}
}codeè il contratto: il tuo programma può farci affidamento, non viene mai riformulato.messageè un'indicazione in inglese per gli sviluppatori e può cambiare. Non mostrarla al ristoratore.request_idè anche nell'intestazioneX-Request-Id; con questo trovi la richiesta nel registro.paramindica il campo errato, anche in profondità nel corpo:products[3].price.
Tutti i codici
| Codice | HTTP | Significato | Cosa fa il tuo programma |
|---|---|---|---|
invalid_request | 400 | Un parametro o il corpo non è corretto: manca un campo obbligatorio, un tipo è sbagliato, il JSON non è valido, oppure la richiesta contiene un campo o un parametro di query che l'operazione non conosce. param indica il campo, se si tratta di un campo. | Non ripetere la richiesta invariata. Correggila in base a param e message. |
| 401 | Nessun token, un token scaduto o revocato, il collegamento non esiste più oppure l'app è bloccata. | Ottieni un nuovo token di accesso con il token di aggiornamento e riprova una volta. Se fallisce con invalid_grant, il collegamento non esiste più; con unauthorized_client l'app è bloccata: conserva i token e riprova più tardi. | |
account_inactive | 403 | L'abbonamento del locale non è attivo. GET /me e DELETE /connection funzionano comunque. | Non ripetere subito. Riprova più tardi o informa il locale. |
insufficient_scope | 403 | Al collegamento manca il permesso per questa operazione. WWW-Authenticate lo indica. | Chiedi al titolare di collegare di nuovo la tua app con questo permesso. Fino ad allora disattiva la funzione. |
plan_required | 403 | Il piano del locale non include la funzione, per esempio le prenotazioni nel piano Starter. | Disattiva la funzione per questo locale e mostra all'utente il motivo. |
not_found | 404 | L'oggetto non esiste, o non in questo locale. Vale anche per un percorso sconosciuto. | Controlla identificativo e percorso. Rimuovi dal tuo archivio un oggetto eliminato. |
method_not_allowed | 405 | Questo metodo non esiste su questo percorso. | Confronta metodo e percorso con il riferimento. |
conflict | 409 | Gli stessi dati sono stati modificati altrove nello stesso momento, per esempio dal locale nella dashboard, tre volte di fila. La richiesta in sé è corretta. | Aspetta Retry-After secondi e invia di nuovo la stessa richiesta invariata, con lo stesso Idempotency-Key. |
external_id_taken | 409 | Questo external_id è già associato, per la tua app in questo locale, a un altro oggetto dello stesso tipo. | Con ?external_id= controlla quale oggetto lo usa e correggi l'associazione nel tuo sistema. |
idempotency_in_progress | 409 | Una prima richiesta con lo stesso Idempotency-Key è ancora in corso. | Aspetta un momento e riprova con la stessa chiave. |
invalid_transition | 409 | Questo cambio di stato non è possibile, per esempio all'indietro o da uno stato concluso. Per le prenotazioni message indica gli stati possibili in quel momento. | Rileggi l'oggetto: di solito è già stato modificato altrove. Poi decidi. |
| 409 | L'orario non è un orario prenotabile del giorno o è già passato, oppure a quell'orario non c'è posto per questo numero di persone o per questi tavoli. | Recupera gli orari liberi con GET /reservations/availability e proponine uno. | |
stage_not_used | 409 | Il locale non usa questo stato, per esempio preparing. GET /store indica quelli usati sotto order_statuses. | Salta il passo e passa allo stato successivo. |
payload_too_large | 413 | Il corpo è più grande di quanto l'operazione accetta, contato in byte: 64 KB, 2 MB per PUT /menu. | Dividi la richiesta, per esempio il menu in più sincronizzazioni con merge. |
idempotency_key_reused | 422 | Lo stesso Idempotency-Key è arrivato con una richiesta diversa (metodo, percorso o corpo). | Usa una nuova chiave per ogni nuova operazione. Cerca l'errore nel programma. |
limit_exceeded | 422 | È stato superato un limite, per esempio troppi piatti in una sincronizzazione. | Dividi o riduci la richiesta. I limiti sono indicati nella guida corrispondente. |
rate_limited | 429 | Più di 120 richieste al minuto per questo collegamento. | Aspetta Retry-After secondi, poi continua. Distribuisci le richieste nel tempo. |
server_error | 500 | Qualcosa è andato storto da parte di Flavoso. | Riprova con intervalli crescenti, per i POST con lo stesso Idempotency-Key. Se il problema persiste, invia il request_id al supporto. |
Gestire gli errori
Un errore ti dice se riprovare serve. 401 significa: rinnova il token. 403 significa: avvisa il titolare, non riprovare. 409 di solito significa: l'oggetto è cambiato altrove, quindi rileggilo. Ogni altro 4xx significa: la richiesta è sbagliata, quindi non ripeterla invariata.
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
}Quando riprovare
| Caso | Riprovare? |
|---|---|
| Errore di rete, timeout | Sì, con lo stesso Idempotency-Key. |
429 rate_limited | Sì, dopo Retry-After secondi. |
500 server_error, 502, 503, 504 | Sì, con intervalli crescenti. |
409 idempotency_in_progress, 409 conflict | Sì, dopo una breve pausa (Retry-After), invariata. |
401 unauthorized | Una volta, dopo aver rinnovato il token. |
| 400, 403, 404, 413, 422, altri 409 | No, non senza modifiche. |
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));
}
}Con il supporto
Scrivi a support@flavoso.com indicando il request_id (o X-Request-Id), più l'ora e l'operazione. Così troviamo la richiesta. Lo stesso identificativo è nel tuo registro delle richieste.
Qualcosa non è chiaro? Scrivi a support@flavoso.com indicando l'identificativo di X-Request-Id. Così troviamo la tua richiesta nel registro.