Flavoso APIGrundlagenFehler
Fehler
Jeder Fehler hat dieselbe Form. Hier stehen alle Codes mit HTTP-Status und was dein Programm tun soll.
Die Form
Ein Fehler ist immer ein Objekt error mit code, message und request_id, bei invalid_request oft dazu param:
{
"error": {
"code": "invalid_request",
"message": "price must be an integer amount in cents.",
"request_id": "req_4f0c2a9d1e7b3c5a8f6d2e10",
"param": "products[3].price"
}
}codeist der Vertrag: darauf darf sich dein Programm verlassen, er wird nicht umformuliert.messageist ein englischer Hinweis für Entwickler und kann sich ändern. Zeig ihn nicht dem Wirt.request_idsteht auch im KopfX-Request-Id; damit findest du die Anfrage im Protokoll.paramnennt das fehlerhafte Feld, auch tief im Rumpf:products[3].price.
Alle Codes
| Code | HTTP | Bedeutung | Was dein Programm tut |
|---|---|---|---|
invalid_request | 400 | Ein Parameter oder der Rumpf stimmt nicht: Pflichtfeld fehlt, falscher Typ, ungültiges JSON, oder die Anfrage enthält ein Feld oder einen Abfrageparameter, den die Operation nicht kennt. param nennt das Feld, wenn es eins ist. | Nicht unverändert wiederholen. Anfrage anhand von param und message korrigieren. |
| 401 | Kein Token, ein abgelaufenes oder widerrufenes Token, die Verbindung besteht nicht mehr, oder die App ist gesperrt. | Mit dem Refresh-Token ein neues Access-Token holen und einmal wiederholen. Scheitert das mit invalid_grant, besteht die Verbindung nicht mehr; mit unauthorized_client ist die App gesperrt: Tokens behalten und später erneut versuchen. | |
account_inactive | 403 | Das Abo des Betriebs läuft nicht. GET /me und DELETE /connection funktionieren trotzdem. | Nicht wiederholen. Später erneut versuchen oder den Betrieb informieren. |
insufficient_scope | 403 | Der Verbindung fehlt das Recht für diese Operation. WWW-Authenticate nennt es. | Den Inhaber bitten, deine App mit diesem Recht neu zu verbinden. Bis dahin die Funktion abschalten. |
plan_required | 403 | Der Tarif des Betriebs enthält die Funktion nicht, etwa Reservierungen im Tarif Starter. | Die Funktion für diesen Betrieb abschalten und dem Nutzer den Grund zeigen. |
not_found | 404 | Das Objekt gibt es nicht, oder nicht in diesem Betrieb. Gilt auch für einen unbekannten Pfad. | Kennung und Pfad prüfen. Ein gelöschtes Objekt aus deinem Speicher entfernen. |
method_not_allowed | 405 | Diese Methode gibt es an diesem Pfad nicht. | Methode und Pfad mit der Referenz vergleichen. |
conflict | 409 | Dieselben Daten wurden im selben Moment woanders geändert, etwa vom Betrieb im Dashboard, dreimal hintereinander. Die Anfrage selbst ist richtig. | Retry-After Sekunden warten und dieselbe Anfrage unverändert noch einmal schicken, mit demselben Idempotency-Key. |
external_id_taken | 409 | Diese external_id hängt bei deiner App in diesem Betrieb schon an einem anderen Objekt derselben Art. | Mit ?external_id= nachsehen, welches Objekt sie trägt, und die Zuordnung in deinem System korrigieren. |
idempotency_in_progress | 409 | Eine erste Anfrage mit demselben Idempotency-Key läuft noch. | Kurz warten und mit demselben Schlüssel wiederholen. |
invalid_transition | 409 | Dieser Statuswechsel geht nicht, etwa rückwärts oder aus einem abgeschlossenen Stand. Bei Reservierungen nennt message die Stände, die gerade gehen. | Das Objekt neu lesen: es wurde meist woanders schon geändert. Dann entscheiden. |
| 409 | Die Zeit ist keine buchbare Zeit des Tages oder liegt in der Vergangenheit, oder zu dieser Zeit ist für diese Personenzahl oder diese Tische kein Platz frei. | Mit GET /reservations/availability freie Zeiten holen und eine davon anbieten. | |
stage_not_used | 409 | Der Betrieb benutzt diesen Stand nicht, etwa preparing. GET /store nennt die benutzten unter order_statuses. | Den Schritt überspringen und zum nächsten Stand schalten. |
payload_too_large | 413 | Der Rumpf ist größer, als die Operation annimmt, gezählt in Bytes: 64 KB, bei PUT /menu 2 MB. | Die Anfrage aufteilen, etwa die Karte in mehrere Abgleiche mit merge. |
idempotency_key_reused | 422 | Derselbe Idempotency-Key kam mit einer anderen Anfrage (Methode, Pfad oder Rumpf). | Für jede neue Operation einen neuen Schlüssel nehmen. Den Fehler im Programm suchen. |
limit_exceeded | 422 | Eine Grenze ist überschritten, etwa zu viele Gerichte in einem Abgleich. | Die Anfrage aufteilen oder kürzen. Die Grenzen stehen beim jeweiligen Leitfaden. |
rate_limited | 429 | Mehr als 120 Anfragen in der Minute für diese Verbindung. | Retry-After Sekunden warten, dann weiter. Abfragen über die Zeit verteilen. |
server_error | 500 | Bei Flavoso ist etwas schiefgegangen. | Mit wachsendem Abstand wiederholen, bei POST mit demselben Idempotency-Key. Hält es an, request_id an den Support. |
Fehler behandeln
Ein Fehler sagt dir, ob Wiederholen hilft. 401 heißt: Token erneuern. 403 heißt: dem Inhaber Bescheid geben, nicht wiederholen. 409 heißt meistens: das Objekt hat sich woanders geändert, also neu lesen. Jeder andere 4xx heißt: die Anfrage stimmt nicht, also nicht unverändert wiederholen.
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
}Wann wiederholen
| Fall | Wiederholen? |
|---|---|
| Netzfehler, Zeitüberschreitung | Ja, mit demselben Idempotency-Key. |
429 rate_limited | Ja, nach Retry-After Sekunden. |
500 server_error, 502, 503, 504 | Ja, mit wachsendem Abstand. |
409 idempotency_in_progress, 409 conflict | Ja, nach einer kurzen Pause (Retry-After), unverändert. |
401 unauthorized | Einmal, nach dem Erneuern des Tokens. |
| 400, 403, 404, 413, 422, übrige 409 | Nein, nicht unverändert. |
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));
}
}Mit dem Support
Schreib an support@flavoso.com und nenne die request_id (oder X-Request-Id), dazu Zeit und Operation. Damit finden wir die Anfrage. Dieselbe Kennung steht in deinem Anfrage-Protokoll.
Etwas unklar geblieben? Schreib an support@flavoso.com und nenne die Kennung aus X-Request-Id. Damit finden wir deine Anfrage im Protokoll.