Aller au contenu

Erreurs

L’API utilise les codes de statut HTTP conventionnels et renvoie une enveloppe d’erreur structurée à chaque échec, afin que vous puissiez vous appuyer sur des champs lisibles par une machine plutôt que d’analyser du texte.

{
"error": {
"type": "invalid_request_error",
"code": "bad_request",
"message": "limit must be a positive integer.",
"param": "limit",
"doc_url": "https://docs.useservice.app/api/errors#bad_request"
}
}
Champ Description
type Catégorie générale de l’erreur (voir ci-dessous). Toujours présent.
code Un code machine spécifique et stable identifiant l’échec. Toujours présent.
message Une explication lisible par un humain, en anglais quelle que soit la langue du restaurant, destinée à la journalisation et au débogage. Ne l’affichez pas aux clients et ne vous appuyez pas dessus pour brancher votre logique.
param Le paramètre de requête en cause — présent uniquement lorsqu’un paramètre précis a causé l’erreur. Omis sinon (il n’est pas envoyé à null).
doc_url Un lien direct vers l’entrée de ce code sur cette page (https://docs.useservice.app/api/errors#<code>). Toujours présent.
violations Présent uniquement sur un refus lié aux règles de réservation. Une entrée par règle enfreinte parmi les contrôles atteints — voir Ce qu’un refus ne peut pas vous dire.

Chaque réponse — succès comme erreurs — renvoie également l’en-tête Service-Version qui l’a servie.

type Signification
invalid_request_error La requête était mal formée, refusée par une règle, ou visait quelque chose qui n’existe pas. Couvre les 400, 403, 404, 409 et 422.
authentication_error La clé d’API est manquante, mal formée ou invalide (401).
rate_limit_error Vous avez dépassé la limite de débit (429).
api_error Un problème est survenu du côté de Service, ou chez un prestataire dont Service dépend (500, 502).

type est dérivé du statut : c’est la branche grossière. code est celui sur lequel écrire votre logique.

Chaque clé d’API porte une liste explicite de portées, choisie par le restaurant au moment de la création de la clé. Les portées sont plates : une portée qui paraît plus large n’en implique jamais une plus étroite, et une clé créée avant l’existence des portées ne porte que reservations:read. Une clé ne peut pas élargir ses propres portées après coup — le restaurant crée une nouvelle clé.

Un appel qui exige une portée absente de la clé est refusé par un 403 insufficient_scope, jamais par une réponse silencieusement appauvrie. Les données client à l’intérieur d’une réponse que vous pouvez lire sont la seule exception : les contact_email, contact_phone, guest_notes et internal_notes d’une réservation, et les notes d’une entrée de file d’attente, sont omis sans guests:read, pas refusés. Voir Données client d’une réservation.

Portée Autorise
reservations:read Lire les réservations et leurs événements, les disponibilités, la configuration, les salles, les options et la file d’attente.
reservations:write Créer, modifier et annuler des réservations ; créer et libérer des options. Il n’existe pas de portée distincte pour les options : une clé qui peut réserver peut poser une option.
reservations:write:override Trois champs. Envoyer le tableau override, qui lève une règle de réservation fixée par le restaurant. Positionner excluded_from_shift_limits: true, qui exclut définitivement une réservation des plafonds de couverts du service. Envoyer entire_section_id sur une création, qui réserve toutes les tables d’une section, y compris une section que le restaurant ne propose pas à la réservation en ligne. Séparée de reservations:write pour qu’une intégration ordinaire n’en fasse aucune des trois par accident.
guests:read Lire les fiches clients : GET /v1/guests, la valeur guest de expand, et sa valeur modification_token sur POST /v1/reservations. Aussi les données client de toutes les réservations — y compris celles que la clé a créées — et de toutes les entrées de file d’attente. Le jeton est la clé du client pour sa page de réservation, étape carte comprise : voir Le jeton du client est sa clé.

La portée requise par chaque point de terminaison

Section intitulée « La portée requise par chaque point de terminaison »
Point de terminaison Portée
GET /v1/availability, GET /v1/availability/months reservations:read
GET /v1/configuration reservations:read
GET /v1/sections reservations:read
GET /v1/reservations, GET /v1/reservations/{id}, GET /v1/reservations/{id}/events reservations:read
POST /v1/reservations, PATCH /v1/reservations/{id}, POST /v1/reservations/{id}/cancel reservations:write
GET /v1/holds, GET /v1/holds/{id} reservations:read
POST /v1/holds, DELETE /v1/holds/{id} reservations:write
GET /v1/waitlist-entries, GET /v1/waitlist-entries/{id} reservations:read
GET /v1/guests, GET /v1/guests/{id} guests:read

Une entrée de file d’attente exprime une demande de table et ne porte aucune fiche client propre : la file se lit avec reservations:read. Le client qu’elle désigne, non : on l’atteint avec expand=guest et la réponse exige alors guests:read, quel que soit le point de départ. La demande du client dans les notes de l’entrée l’exige aussi, et elle est absente sans elle.

Deux portées sont exigées par un élément de la requête plutôt que par le point de terminaison, réparties sur quatre champs, et le refus nomme ce champ dans param :

Champ Portée param
override: [...] sur une écriture reservations:write:override override
excluded_from_shift_limits: true reservations:write:override excluded_from_shift_limits
entire_section_id sur une création reservations:write:override entire_section_id
expand=guest, et expand=modification_token sur POST /v1/reservations guests:read expand

Une seule portée couvre les trois premiers, et chacun reste refusé séparément : le param vous dit pour quel champ la clé a été écartée, pas quelle portée lui manque. Porter reservations:write:override les accorde tous les trois d’un coup.

C’est toute la raison d’être du param sur insufficient_scope. Sans param, votre clé ne peut pas appeler ce point de terminaison et renvoyer la requête ne sert à rien. Avec un param, votre clé peut l’appeler et seul ce champ est hors d’atteinte : retirer le champ et renvoyer la requête est un réessai qui mérite d’être automatisé.

Envoyer override: [] ou excluded_from_shift_limits: false ne demande rien : aucune de ces deux valeurs n’exige la portée. Seule l’affirmation l’exige. Sur une réservation de section entière, excluded_from_shift_limits: true est la valeur par défaut : l’envoyer n’est refusé que là où entire_section_id le serait déjà.

La vérification de entire_section_id a lieu avant la recherche de la section : une clé sans la portée reçoit le 403, que l’identifiant existe ou non.

Lorsqu’une création, une modification ou une option enfreint les règles de réservation du restaurant, la réponse a toujours la même forme :

  • le code de premier niveau vaut toujours booking_rules_violated,
  • violations porte une entrée par règle enfreinte,
  • le tableau est présent même lorsqu’une seule règle est enfreinte.
{
"error": {
"type": "invalid_request_error",
"code": "booking_rules_violated",
"message": "The booking breaks one or more of this restaurant's booking rules. See `violations` for each one, and whether it can be overridden.",
"doc_url": "https://docs.useservice.app/api/errors#booking_rules_violated",
"violations": [
{
"code": "party_size_too_large",
"message": "Party size exceeds the maximum",
"param": "party_size",
"overridable": true
},
{
"code": "cutoff_passed",
"message": "Reservation cutoff has passed",
"overridable": true
}
]
}
}

Le code de premier niveau ne nomme jamais une règle, et le message de premier niveau est la même phrase pour une violation et pour six. Lisez le tableau. Un client qui lit le code de premier niveau fonctionne jusqu’au jour où une réservation enfreint deux règles : il en renvoie une et se fait refuser par celle qu’il n’a pas lue.

Champ Description
code La règle enfreinte. Unique dans une réponse : le même code n’apparaît jamais deux fois.
message En anglais, pour vos journaux. Une phrase fixe par code, sans aucun chiffre : la limite franchie se lit sur GET /v1/configuration (min_party_size, max_party_size, max_advance_days) ou sur le service dans GET /v1/availability (effective_cutoff_minutes). La seule exception est start_time_off_grid, dont le message nomme l’heure envoyée et les horaires d’arrivée du service.
param Le champ de requête à changer, lorsqu’un seul champ est en cause. Omis sinon.
overridable Indique si cette violation peut être levée en renvoyant la requête avec ce code dans override. Toujours présent sur chaque entrée.
conflicts Présent uniquement sur section_occupied : une entrée par table occupée, sous la forme table, start_time et end_time.

Les violations ne portent pas de doc_url propre. Construisez-la à partir du code, comme le fait l’enveloppe : https://docs.useservice.app/api/errors#<code de la violation>.

violations liste chaque règle enfreinte parmi les contrôles qui ont tourné, et trois contrôles restent en dehors de ce groupe :

  • no_service_at_this_time est vérifié en premier. Toutes les autres règles sont jugées par rapport à un service : lorsqu’aucun service ne couvre la date et l’heure, rien d’autre n’est vérifié et cette violation arrive seule.
  • start_time_off_grid est vérifié ensuite. Un service couvre l’heure mais n’accueille pas d’arrivée à cette minute. Toutes les autres règles sont jugées par rapport à un horaire d’arrivée, si bien que cette violation arrive seule, elle aussi.
  • La recherche de table passe en dernier, une fois chaque règle respectée ou levée. Ses refus, no_table_available et no_table_available_in_section, ne sont jamais listés à côté des règles qui la précèdent.

Une réservation refusée pour party_size_too_large puis renvoyée avec ce code dans override peut ainsi revenir avec un second 422, qui nomme cette fois no_table_available. La levée a fonctionné ; le restaurant n’a aucune table pour ce groupe à cette heure. Un écran d’accueil qui propose « lever et réserver » doit traiter cette seconde réponse et dire à l’opérateur que la réservation ne peut pas être placée, sans lui proposer une nouvelle levée.

overridable: true signifie qu’une clé portant reservations:write:override peut renvoyer la réservation à l’identique avec ce code dans un tableau override, et que la règle est alors levée au lieu d’être appliquée. La liste des codes levables est fixée dans l’API, pas par restaurant.

Fenêtre de terminal
curl -X POST "https://api.useservice.app/v1/reservations" \
-H "Authorization: Bearer $SERVICE_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"service_date": "2026-10-04",
"start_time": "20:00",
"party_size": 9,
"guest": { "first_name": "Marie", "last_name": "Dupont" },
"override": ["party_size_too_large"]
}'

Quatre points décident du succès de cet appel :

  • Réessayez une fois, pas en boucle. Relevez les codes levables dans violations, décidez, renvoyez une fois. Un second refus est un autre problème de réservation, pas une liste plus longue — le plus souvent la recherche de table, que le premier refus ne peut pas signaler.
  • Renvoyez le même Idempotency-Key que la première fois. La tentative refusée n’a rien créé, et la clé est ce qui empêche un délai d’attente de produire une double réservation.
  • Un code non levable donne un 400, pas un silence. Placer date_in_past dans override refuse toute la requête avec bad_request et param: "override", et le message précise si le code est une vraie règle qui ne peut jamais être levée ou s’il ne désigne aucune règle.
  • Le tableau peut être envoyé par anticipation. Une levée n’est enregistrée que lorsqu’elle a effectivement levé quelque chose : un override sur une réservation qui n’enfreint aucune règle ne coûte rien et n’est pas consigné.

Trois refus sont assez proches pour être confondus : ils portent donc des codes différents. Sur une écriture, tous trois arrivent dans violations, sous la même ombrelle.

Code Situation overridable param
no_service_at_this_time Aucun service ne couvre cette date et cette heure. Le restaurant est fermé à ce moment-là, ou l’heure tombe entre deux services. false absent
start_time_off_grid Un service est ouvert mais n’accueille pas d’arrivée à cette minute : l’heure tombe entre deux horaires d’arrivée, ou après le dernier. false start_time
slot_no_longer_available Un service est ouvert et son plafond de couverts est atteint. true party_size

Les remèdes diffèrent, et c’est pourquoi les codes diffèrent. Pour no_service_at_this_time et start_time_off_grid, relisez GET /v1/availability et envoyez une heure qu’il publie : rien d’autre ne fera aboutir cette réservation, et nommer l’un de ces codes dans override donne un 400. Pour slot_no_longer_available, envoyez un groupe plus petit, une autre heure, ou la même réservation avec le code dans override si votre clé détient la portée.

overridable répond toujours à la question sur laquelle vous agissez, et il est présent sur chaque violation pour cela. Les codes diffèrent aussi pour que vous puissiez journaliser, compter et alerter séparément : un restaurant fermé à l’heure que votre système demande sans cesse n’est pas le même problème qu’un restaurant qui affiche complet.

authentication_error · 401 Unauthorized. Aucun en-tête Authorization: Bearer … n’a été envoyé. Ajoutez l’en-tête — voir Authentification.

authentication_error · 401 Unauthorized. Une clé a été fournie mais elle est invalide, révoquée, expirée ou inconnue. Créez une nouvelle clé si la vôtre ne fonctionne plus. Renvoyer la même clé ne peut pas réussir.

invalid_request_error · 403 Forbidden. La clé est valide et le restaurant existe, mais son abonnement n’inclut pas l’accès à l’API. Il s’agit d’un état de facturation et non d’un problème de permissions : élargir les portées de la clé n’y changera rien, et créer une nouvelle clé non plus. Demandez au restaurant de mettre son abonnement à niveau ; l’accès revient à la requête suivante, sans modification de votre côté.

invalid_request_error · 403 Forbidden. La clé est valide et le restaurant est éligible ; la clé ne porte pas la portée exigée par cet appel. Le message nomme la portée manquante.

Lisez param avant de réagir :

  • param absent — la clé ne peut pas appeler ce point de terminaison. Demandez au restaurant une clé portant la portée nommée dans le message. Réessayer ne change rien.
  • param présent — la clé peut appeler le point de terminaison, et seul le champ nommé dans param est hors d’atteinte. Retirez ce champ et renvoyez la requête.

Voir Portées exigées par un seul champ pour les quatre champs concernés.

invalid_request_error · 400 Bad Request. Un paramètre, un filtre, un curseur, un en-tête ou un champ du corps est mal formé, inconnu ou manquant. param le nomme. Causes fréquentes : une valeur expand inconnue, un filtre date mal formé, un locale que le restaurant ne publie pas, un section_id ou un entire_section_id qui n’est pas un identifiant sec_…, un entire_section_id envoyé avec section_id ou hold_id, une duration autre que until_shift_end ou envoyée sans entire_section_id, une valeur metadata qui n’est pas un objet, une entrée override qui n’est pas un code levable, une valeur excluded_from_shift_limits qui n’est pas un booléen JSON, une Service-Version non prise en charge (param est le nom de l’en-tête, Service-Version), et un corps de PATCH ne contenant aucun champ modifiable.

Les filtres de liste en refusent deux autres par leur nom. service_date sur GET /v1/reservations ou GET /v1/holds est un 400 avec param: "service_date" : le filtre est date, ou l’intervalle date[gte] et date[lte]. Un status sur GET /v1/reservations hors de l’énumération publiée est aussi un 400, et late n’en fait pas partie.

Un corps d’écriture est lu strictement. Un champ que le point de terminaison ne lit pas est un 400 qui le nomme dans param, au lieu d’un champ ignoré en silence. Dans guest, le nom est pointé (guest.vip), et un corps qui est un tableau JSON plutôt qu’un objet reçoit param: "body". Les clés contenues dans metadata ne sont jamais vérifiées.

Les champs d’écriture vont dans le corps JSON. Un champ envoyé dans la chaîne de requête à la place — PATCH /v1/reservations/{id}?party_size=6 — est un 400 qui le nomme dans param, même quand le corps est vide, au lieu d’une modification appliquée depuis l’URL. Les paramètres de requête qui ne sont pas des champs d’écriture, comme expand, sont lus comme avant.

Trois autres viennent du contenu du corps :

  • un corps qui n’est pas du JSON valide — param vaut body,
  • un start_time qui n’est pas une heure de la journée, comme 8pm ou 25:00 — param vaut start_time,
  • un guest.phone qu’aucun opérateur ne pourrait acheminer — param vaut guest.phone. Un numéro sans indicatif de pays est lu dans le pays du restaurant.

Corrigez la requête. La renvoyer telle quelle échouera de la même manière.

PATCH /v1/reservations/{id} répond également bad_request lorsque le corps porte un champ fixé à la création : guest, guest_id, guest_notifications, metadata, hold_id, entire_section_id et duration — ou guarantee_consent_amount_cents, qui appartient à la page de réservation du client et ne peut pas être envoyé en son nom (voir guarantee_consent_guest_only). param nomme le champ et message explique pourquoi. Ces champs sont refusés plutôt qu’ignorés, afin qu’un partenaire qui envoie guest_notifications sur un PATCH n’obtienne jamais un 200 laissant croire que le réglage a changé.

invalid_request_error · 422 Unprocessable Content. L’objet metadata de POST /v1/reservations ou de POST /v1/holds compte plus de 50 clés, ou dépasse 4 096 octets une fois sérialisé. Le nom du code vient du widget de réservation, qui partage cette limite. Envoyez moins : gardez dans metadata une référence vers votre propre enregistrement, pas l’enregistrement.

invalid_request_error · 422 Unprocessable Content. Une valeur était bien formée mais l’enregistrement l’a refusée — le message porte sa propre objection. Lisez le message, corrigez la valeur.

invalid_request_error · 422 Unprocessable Content. La réservation ne porte pas de first_name, ou pas de last_name. Les deux sont exigés sur une création qui envoie un objet guest : une table dont personne ne porte le nom ne peut pas être accueillie à la porte.

Une création qui identifie une fiche existante avec guest_id en est dispensée. Les noms sont déjà sur cette fiche, et les redemander rendrait guest_id inutile pour l’intégration même à laquelle il sert. required_guest_fields, sur GET /v1/configuration, décrit la même règle.

require_guest_email et require_guest_phone sont publiés sur GET /v1/configuration à titre indicatif : ils décrivent le formulaire du restaurant et n’engagent pas cette API. L’adresse e-mail et le téléphone y sont donc facultatifs, à une exception près, celle d’un créneau portant une garantie par carte. Voir email_required_for_guarantee.

Idempotency-Key est exigé sur POST /v1/reservations et POST /v1/holds, et facultatif sur PATCH /v1/reservations/{id} et POST /v1/reservations/{id}/cancel. Envoyez une valeur unique par tentative, et la même valeur lorsque vous réessayez cette tentative. Les clés sont mémorisées pendant 72 heures.

Un nouvel essai d’une requête déjà terminée reçoit la réponse de cette requête : le statut et le corps d’origine, à l’identique, l’en-tête Location quand l’original en portait un, et l’en-tête Idempotent-Replayed: true. Une première écriture ne porte jamais cet en-tête. C’est ce qui vous permet de distinguer « cet appel a créé la réservation » de « un appel précédent l’a créée » avant d’agir sur un 201.

Le corps est celui enregistré la première fois, y compris tout expand demandé par la première requête, quoi que demande le nouvel essai. Une clé ne lit pas pour autant au-delà de ses portées : l’empreinte inclut la clé d’API qui a envoyé la requête, si bien qu’aucune autre clé ne peut la rejouer, et les portées d’une clé sont fixées à sa création.

invalid_request_error · 400 Bad Request. Une écriture qui exige l’en-tête a été envoyée sans lui. C’est un 400 et non un 422 parce que la réservation n’a jamais été interprétée : ajoutez l’en-tête et renvoyez la requête inchangée.

invalid_request_error · 400 Bad Request. L’en-tête était présent mais dépasse la longueur maximale, ou contient des caractères de contrôle. Envoyez une chaîne imprimable — un UUID par tentative est le choix habituel.

invalid_request_error · 409 Conflict. Une requête antérieure portant cette clé est encore en cours. Attendez un instant et réessayez avec la même clé. Créer une nouvelle clé ici est la façon de produire une double réservation.

invalid_request_error · 409 Conflict. Cette clé a déjà servi pour une requête différente. La clé est l’empreinte d’une tentative : la réutiliser pour une autre réservation est refusé plutôt que répondu par la première réservation. Créez une nouvelle clé pour la nouvelle requête.

Deux clés générées indépendamment peuvent se heurter sur une même chaîne : les clés sont cloisonnées par restaurant et non par clé d’API, si bien qu’une autre intégration sur le même restaurant peut consommer une chaîne que vous auriez utilisée. Des valeurs aléatoires par tentative évitent ce cas.

Sur POST /v1/reservations, PATCH /v1/reservations/{id} et POST /v1/holds, tous les codes de cette section arrivent à l’intérieur de violations, sous l’ombrelle booking_rules_violated, et jamais comme code de premier niveau.

GET /v1/availability est la seule exception. Il vérifie quatre de ces règles avant de calculer un créneau, et répond à un échec par un 422 dont le code de premier niveau est la règle, sans tableau violations ni param : date_in_past, party_size_too_small, party_size_too_large et advance_window_exceeded. Une lecture ne réserve rien : il n’y a rien à lever, changez la requête. GET /v1/availability/months ne renvoie aucun d’eux : il ne prend pas de nombre de couverts, et signale un jour passé ou hors de la fenêtre par past ou outside_window au lieu de refuser.

invalid_request_error · 422 Unprocessable Content. L’ombrelle de tout refus lié aux règles de réservation. Lisez violations ; voir Refus liés aux règles de réservation.

Levable. Un service couvre cette heure et il est complet : son plafond de couverts n’a plus la place pour un groupe de cette taille. Envoyez un groupe plus petit, une autre heure, ou la même réservation avec ce code dans override si votre clé détient la portée. param vaut party_size.

Cela ne veut pas dire que le restaurant est fermé à ce moment-là : c’est no_service_at_this_time. Voir Aucun service, ou un service complet.

Levable. La réservation en ligne pour ce service est close depuis un certain nombre de minutes avant son début. La limite est propre à chaque service, et celle réellement appliquée est publiée sous effective_cutoff_minutes sur chaque service dans GET /v1/availability — lisez-la là plutôt que dans la valeur par défaut du restaurant, qu’un service peut remplacer.

Levable. La date dépasse l’horizon auquel le restaurant accepte les réservations en ligne. max_advance_days sur GET /v1/configuration porte cet horizon, ce qui vous permet d’écarter la date avant de l’envoyer.

Levable. En dessous du minimum du restaurant. min_party_size figure sur GET /v1/configuration. param vaut party_size.

Levable. Au-dessus du maximum du restaurant. max_party_size figure sur GET /v1/configuration. param vaut party_size.

Levable. Le personnel a marqué ce créneau ou ce service comme complet depuis le back-office. La table peut être matériellement libre, et c’est pourquoi ce refus est levable : le restaurant a cessé de vendre le créneau plutôt que de manquer de place.

Levable. Le service est ouvert et visible, et n’accepte aucune réservation en ligne (son mode en ligne est l’affichage seul). Les disponibilités publient ces services avec display_only, ce qui vous permet de les présenter comme une information plutôt que de les proposer.

Non levable. Ni un service ni une exception de date ne couvre cette date et cette heure : il n’y a rien à réserver — le restaurant est fermé à ce moment-là, ou l’heure tombe entre deux services. GET /v1/availability publie les heures qui existent ; lisez-le et envoyez l’une d’elles.

Nommer ce code dans override donne un 400, et non un silence. Il n’y a ici aucun arbitrage de capacité à lever : la réservation n’a aucun service auquel appartenir, et aucune clé ni aucune portée n’y change quoi que ce soit.

Non levable. Un service est ouvert à cette heure mais n’accueille pas d’arrivée à cette minute : l’heure tombe entre deux de ses horaires d’arrivée, ou après le dernier. param vaut start_time. Le message, en anglais, nomme l’heure envoyée et les horaires d’arrivée du service, par exemple 20:07 is not one of this service's seating times: it seats every 30 minutes from 19:00 to 21:00. Envoyez un start_time que GET /v1/availability propose pour ce service.

Il est vérifié à la création, à la pose d’une option et sur un PATCH qui change la date ou l’heure. Un PATCH qui conserve les deux n’est pas vérifié, car l’équipe du restaurant peut placer une réservation à n’importe quelle minute. La conversion d’une option n’est pas vérifiée non plus : l’option a déjà réservé la place. Comme no_service_at_this_time, il arrive seul, et le nommer dans override donne un 400.

Non levable. La date précède le jour courant du restaurant. Un service déjà passé ne peut pas être vendu ; envoyez une autre date.

Non levable. La date est valide et cette heure de la journée est déjà passée. Distinct de date_in_past parce que le remède diffère : une heure plus tardive le même jour, plutôt qu’un autre jour. param vaut start_time.

Non levable. Les couverts étaient disponibles et aucune table ni combinaison de tables ne peut accueillir ce groupe à cette heure. Le mécanisme de levée ne couvre pas ce refus : toutes les façons de l’honorer produiraient une réservation placée là où personne ne l’a choisi, ou une réservation sans table. Essayez une autre heure, ou un autre nombre de couverts.

Nommer ce code dans override donne un 400 qui dit qu’il s’agit d’une vraie règle jamais levable — et non un 400 qui dit que ce n’est pas un code. Le jugement de capacité que vous pouvez lever est slot_no_longer_available ; ce refus-ci signifie que les couverts étaient là et qu’aucune table ne convient.

Non levable. Le même refus, restreint par le section_id demandé : aucune table desservie par ce service ne se trouve dans cette salle. Retirez section_id et renvoyez la requête pour laisser le restaurant placer le groupe où il veut, ou choisissez une autre salle dans GET /v1/sections.

Sur une réservation de section entière, le même code, avec param: "entire_section_id", signifie que la section n’est pas dessinée sur le plan de salle en vigueur pour ce service. Essayez une autre date ou une autre section.

Non levable. Une réservation de section entière (entire_section_id) a été demandée alors qu’une partie de la section est déjà prise pendant la plage que la réservation occuperait : une réservation sur l’une de ses tables, ou une autre réservation de la section entière. Rien n’est placé ; une réservation de section entière n’est jamais placée sur une partie de la salle. param vaut entire_section_id.

La violation porte conflicts, une entrée par table occupée :

Champ Description
table L’identifiant tbl_… de la table occupée. null lorsque la section n’a aucune table et qu’une autre réservation de la section entière l’occupe.
start_time HH:MM, heure à partir de laquelle la table est prise.
end_time HH:MM, heure jusqu’à laquelle elle est prise. null pour une réservation enregistrée sans heure de fin.

Les entrées sont triées par table, puis par heure de début. Elles ne disent rien de qui occupe la table. Essayez une autre heure ou une autre date, ou demandez au restaurant de déplacer les réservations listées. Nommer ce code dans override est un 400. Voir Réserver une salle entière.

Levable. Le même client détient déjà une réservation vivante sur le même créneau. L’idempotence ne couvre pas ce cas : elle attrape deux fois la même requête, pas deux requêtes différentes pour un même client — un client qui réserve une fois via le widget et une fois chez vous, ou un réessai dont la clé a été régénérée. Une personne qui reçoit légitimement à deux tables existe : c’est un avertissement et non un mur.

Levable. Le créneau porte une garantie par carte, et celle-ci ne peut pas aboutir faute d’adresse e-mail joignant le client. Deux remèdes : envoyer une adresse e-mail du client, ou lever la règle, ce qui réserve sans la garantie. Levez-la délibérément : c’est tout l’intérêt lorsqu’une réservation est prise par téléphone, et le restaurant ne détient alors aucune carte en cas d’absence.

Ce code peut aussi être envoyé par anticipation, avant même d’avoir vu le refus.

Non levable. Vous avez envoyé guest_notifications: "partner_managed" pour un créneau portant une garantie par carte. La garantie est un e-mail : le client reçoit un lien de paiement, un rappel et un reçu, et sans eux la carte n’est jamais enregistrée, l’option expire et la réservation s’annule automatiquement — après que vous lui avez annoncé que la table était à lui.

Deux remèdes, tous deux entre vos mains : abandonner la suppression pour cette réservation, ou envoyer override: ["guarantee_required"], qui réserve sans la garantie et ne laisse aucun lien de paiement à supprimer.

Non levable. Le compte du restaurant n’autorise plus du tout les réservations en ligne. Vous ne devriez pas atteindre ce code, puisque la même condition fait échouer l’authentification sous plan_required : le rencontrer signifie que l’état du compte a changé entre l’authentification et l’écriture. Rien de votre côté ne le corrige.

invalid_request_error · 404 Not Found. Aucune ressource de ce nom pour le restaurant de cette clé, ou aucun point de terminaison de ce nom. Voir 404, et non 403, entre restaurants.

Lorsque l’identifiant qui ne correspond à rien a été envoyé dans le corps ou dans la chaîne de requête — guest_id, hold_id, section_id, entire_section_id, company_id ou excluding — param nomme ce champ.

Un identifiant d’option déjà convertie ou libérée, par vous ou par le restaurant depuis son back-office, répond lui aussi 404 sur POST /v1/reservations { hold_id } : du point de vue de la conversion il n’existe aucune option utilisable, et c’est ce qui empêche de dépenser deux fois la même option.

invalid_request_error · 409 Conflict. La vie de la réservation est terminée : elle est completed ou no_show, ou (sur PATCH seulement) déjà cancelled. Rien de ce que vous enverrez ne la changera. Lisez status et cessez d’écrire.

Annuler une réservation déjà annulée n’est pas ce cas : cela répond 200 avec le même objet terminal, puisque l’objectif est déjà atteint.

invalid_request_error · 409 Conflict. DELETE /v1/holds/{id} sur une option déjà devenue une réservation. Il n’y a plus rien à libérer, et la table reste prise — par une réservation. Le message la nomme ; une option et sa réservation partagent le suffixe de l’identifiant : hold_AbC est devenue resv_AbC. Annulez cette réservation si vous vouliez rendre la table.

C’est délibérément un 409 et non le 404 que renvoie la conversion pour la même option. DELETE pose la question « assurez-vous que ceci ne retient pas une table », et un 404 vous dirait que l’option n’a jamais existé alors que vous détenez en réalité une réservation confirmée.

invalid_request_error · 409 Conflict. PATCH /v1/reservations/{id} sur une réservation faite avec entire_section_id. Toute modification est refusée, y compris guest_notes seul, car une modification replace le groupe sur une table ou une combinaison et réduirait la réservation à une partie de la salle. Deux corps reçoivent une réponse avant ce contrôle : un corps vide, et un corps qui nomme un champ qu’aucun PATCH n’accepte, obtiennent chacun d’abord un 400. Annulez et réservez de nouveau, ou demandez au restaurant de la modifier depuis son back-office. Le lien de gestion du client est refusé de la même façon.

invalid_request_error · 409 Conflict. Une transition de statut que cette API n’avait pas anticipée. Chaque situation que vous pouvez atteindre possède délibérément un code plus précis : celui-ci est le filet de sécurité, pas un code sur lequel brancher. Relisez la réservation et regardez status.

Certains créneaux exigent une empreinte de carte. Une création sur l’un d’eux produit une réservation en awaiting_guarantee et envoie au client un lien de paiement. Une fois la carte enregistrée, la réservation passe à confirmed, ou à pending quand le service demande au restaurant de valider ses réservations. Les codes ci-dessous sont les façons dont cela peut être refusé.

Une carte est demandée par deux voies. Le créneau la demande de lui-même, quoi que vous envoyiez ; ou vous envoyez un objet guarantee sur une création ou un PATCH et la demandez sur un créneau qui n’en aurait pris aucune. Voir Demander une carte vous-même.

Les deux voies divergent quand les paiements par carte du restaurant ne sont pas actifs. Le créneau ne demande rien, aucun code de cette section n’est renvoyé et la création est prise sans garantie — voir Cet établissement demande-t-il une carte ? Un guarantee que vous avez envoyé est refusé, lui, par payments_suspended.

invalid_request_error · 422 Unprocessable Content. Le créneau porte une garantie par carte et la réservation n’a aucune adresse e-mail vers laquelle envoyer le lien de paiement. Envoyez guest.email, ou réutilisez un guest_id dont la fiche en porte déjà une.

C’est le seul endroit où une adresse e-mail du client est obligatoire sur cette API. Partout ailleurs elle est facultative, et c’est pourquoi le code est spécifique plutôt qu’une erreur générique de champ manquant.

Sur une création qui va jusqu’à la vérification des règles de réservation, le même refus arrive dans violations aux côtés de guarantee_required, qui propose l’autre remède. Sur une conversion d’option il arrive à plat, sans tableau.

invalid_request_error · 422 Unprocessable Content. La réservation a atteint l’étape de garantie sans fiche client, ou avec une fiche sans adresse e-mail de contact : le lien de paiement n’a nulle part où aller. Envoyez un client accompagné d’une adresse e-mail.

Distinct de email_required_for_guarantee, qui est vérifié plus tôt et signale le remède qui ne coûte rien. Une création sans adresse reçoit ce refus plus précoce, quelle que soit la voie qui a demandé la carte, et son message nomme la voie. Le code présent est la réponse à un guarantee envoyé sur un PATCH dont la réservation n’a personne à qui écrire.

invalid_request_error · 422 Unprocessable Content. La garantie configurée par le restaurant pour ce créneau se résout à un montant nul ou négatif : il n’y a rien à demander au client. C’est un problème de configuration du restaurant et non de votre requête — signalez-le-lui, ou envoyez guarantee.amount_cents vous-même et fixez le chiffre.

Votre propre guarantee.amount_cents ne produit jamais ce code : un montant nul, négatif ou fractionnaire est refusé à l’entrée, par un 400 qui nomme guarantee.amount_cents. Ce code n’est atteint que là où le montant a été laissé à la politique du restaurant.

invalid_request_error · 422 Unprocessable Content. La garantie n’a pas pu être posée sur l’état de la réservation.

Sur un guarantee envoyé avec un PATCH, cet état est celui de la réservation : une carte ne peut être demandée que sur une réservation à venir, confirmed et sans garantie. Une réservation en attente de validation doit d’abord être validée, et une réservation qui porte déjà une carte doit voir celle-ci libérée depuis le back-office. Relisez la réservation avant de redemander.

Sur le chemin de création il s’agit d’une incohérence interne plutôt que d’un choix de votre requête : réessayez la création une fois, et signalez-le si cela se reproduit.

invalid_request_error · 422 Unprocessable Content. Vous avez demandé une carte avec guarantee, et le restaurant ne peut pas encaisser par carte : sa configuration de paiement est inachevée, ou son compte a été restreint depuis. Il n’existe aucun moyen de recueillir la carte, donc rien n’est écrit.

Le même restaurant répond autrement à un créneau à carte : la réservation est prise et confirmée sans garantie, et vous n’apprenez rien, puisque rien n’a été refusé. La demande explicite est le seul endroit où un compte suspendu est une erreur.

invalid_request_error · 422 Unprocessable Content. Un PATCH aurait relevé le montant garanti d’une réservation dont la carte a été enregistrée via le widget du restaurant — augmenter le nombre de couverts en est la cause habituelle — et un montant plus élevé, c’est un plafond plus élevé que le restaurant pourra un jour débiter. Relever ce plafond exige l’accord du titulaire de la carte sur le nouveau montant : cet accord lui appartient et ne peut pas être donné en son nom. Ni votre clé ni le personnel du restaurant ne peuvent l’affirmer à sa place.

Rien n’a changé. La modification est entièrement annulée et la réservation est exactement telle qu’elle était, y compris tout autre champ envoyé dans le même corps.

Trois voies possibles :

  • Faire un changement qui ne relève pas le montant. Déplacer le jour ou l’heure s’applique, l’échéance d’annulation gratuite suivant le nouveau service, et réduire le nombre de couverts également — l’empreinte est alors recalculée à la baisse sans rien demander à personne, le mandat existant, plus élevé, couvrant déjà le montant réduit.
  • Passer par le restaurant. Le personnel peut modifier le nombre de couverts depuis le back-office ; la garantie conserve son plafond existant, plus bas.
  • Envoyer le client sur sa propre page de réservation, seule surface capable de recueillir un nouveau mandat. GET /v1/configuration publie public_booking_url, et POST /v1/reservations?expand=modification_token renvoie le jeton d’une réservation que vous avez créée.

Une réservation créée par votre clé ne peut pas atteindre ce code. La carte d’une réservation prise par le widget a été saisie par le client, qui a accepté un montant en l’enregistrant, et relever ce montant est un nouvel accord que lui seul peut donner. La carte d’une réservation créée par votre clé a été demandée par le lien de paiement du client (origin: "staff_request"), au montant que les règles du restaurant fixent ou à celui que vous avez demandé. Aucun chiffre n’a été soumis à l’accord de ce client, et la carte garde le montant auquel elle a été demandée pour toute la vie de la réservation. Voir Modifier la réservation avant que la carte soit enregistrée et Demander une carte vous-même.

Vous rencontrez ce code tout de même, parce qu’une synchronisation vous livre les réservations prises par le restaurant via son propre widget, et que votre chemin de modification passe dessus aussi.

details.guarantee_amount_cents n’est pas publié dans l’enveloppe : le montant ne vous est d’aucune utilité, puisqu’il n’existe aucun chiffre que vous pourriez accepter.

invalid_request_error · 403 Forbidden. La réservation porte une empreinte de carte active et sa date limite d’annulation gratuite est passée : elle ne peut plus être modifiée. Cette fenêtre est ce qui empêche un groupe de passer de huit à deux une heure avant le service pour échapper à l’empreinte.

L’annulation reste possible, avec les frais prévus par les conditions du restaurant. Ce 403 porte sur l’état de la réservation et non sur votre clé : aucune portée n’y change rien.

invalid_request_error · 422 Unprocessable Content. Un PATCH ferait passer une réservation sans carte sous une garantie par carte : le groupe dépasse la taille à partir de laquelle le restaurant demande une carte, ou la réservation passe sur un service qui en demande une à chaque réservation. Un PATCH n’a aucune étape qui recueille une carte : il est refusé avant toute écriture. Rien n’a changé, y compris les autres champs du même corps.

Il arrive à plat, pas dans violations, et il n’est jamais levable : le nommer dans override donne un 400.

Annulez puis réservez à nouveau. La création est l’appel qui recueille une carte : la nouvelle réservation arrive en awaiting_guarantee et le client reçoit le lien de paiement. Si votre clé porte reservations:write:override, la création peut à la place lever guarantee_required et réserver sans aucune empreinte. Une modification qui n’entraîne aucune garantie s’applique normalement.

Le lien de gestion du client ne contourne rien. Cette page répond le même refus : elle indique au client que rien n’a été modifié, et qu’il lui faut annuler puis faire une nouvelle réservation ou contacter l’établissement. Aucune modification ne recueille de carte, d’un côté comme de l’autre.

Une garantie libérée, retirée ou expirée compte comme une absence de carte. Deux cas ne sont pas refusés : une réservation dont le créneau et le groupe actuels demandent déjà une garantie qu’elle n’a pas, par exemple une réservation créée en levant guarantee_required, et toute réservation chez un restaurant dont les paiements par carte ne sont pas actifs à ce moment.

rate_limit_error · 429 Too Many Requests. Attendez l’intervalle Retry-After puis réessayez. Les lectures et les écritures puisent dans deux budgets distincts et RateLimit-Resource nomme celui que vous venez de dépenser — voir Limites de débit.

api_error · 502 Bad Gateway. Le prestataire de paiement était injoignable ou a refusé pendant la prise d’empreinte. La réservation n’a pas été créée. Réessayez avec le même Idempotency-Key, en espaçant les tentatives.

api_error · 500. Une erreur inattendue du côté de Service. Réessayez avec un délai exponentiel, et signalez-le-nous si le problème persiste.

Statut Signification
200 OK La requête a réussi.
201 Created Une réservation ou une option a été créée.
304 Not Modified Un GET conditionnel dont le If-None-Match correspond. Voir Étendre les objets.
400 Bad Request La requête n’a jamais été interprétée — une entrée mal formée ou inconnue. Vérifiez param.
401 Unauthorized Clé d’API manquante ou invalide. Voir Authentification.
403 Forbidden La clé, l’abonnement ou l’état de la réservation l’interdit. Réessayer n’y change rien.
404 Not Found Aucune ressource de ce nom pour le restaurant de cette clé, ou aucun point de terminaison de ce nom.
409 Conflict L’état de la ressource refuse l’opération, ou une clé d’idempotence est en cours ou déjà utilisée.
422 Unprocessable Content La requête a été comprise et refusée par une règle. Les règles de réservation arrivent ici avec violations.
429 Too Many Requests Débit limité. Voir Limites de débit.
500 / 502 / 503 Un problème est survenu du côté de Service ou chez un prestataire. Réessayez avec un délai exponentiel.

Si vous demandez une ressource qui existe mais appartient à un autre restaurant, l’API renvoie 404 Not Found, et non 403 Forbidden. Du point de vue de votre clé, la ressource n’existe pas. Cela évite de divulguer l’existence des données d’autres restaurants. Un identifiant valide qui devrait « fonctionner » selon vous mais qui renvoie 404 appartient presque toujours à un autre restaurant, ou à une autre clé.

  • Branchez sur code, repliez-vous sur type, ne vous appuyez jamais sur message.
  • Sur un 422 booking_rules_violated, lisez violations et branchez entrée par entrée sur overridable. Le code de premier niveau ne vous apprend rien d’autre.
  • Réessayez les 429 après Retry-After, et les 500 / 502 avec un délai exponentiel — sur une écriture, toujours avec le même Idempotency-Key.
  • Réessayez 409 idempotency_request_in_progress avec la même clé. Tous les autres 409 exigent que vous relisiez d’abord la ressource.
  • Un 2xx portant Idempotent-Replayed: true est la réponse d’une tentative précédente, pas une nouvelle écriture. Voir Idempotence.
  • Ne réessayez jamais un 400, 401, 403 ou 404 inchangé. Chacun appelle une autre requête, une autre clé, ou une action du restaurant.

Cette page liste chaque code renvoyé par l’API Service. Le back-office et le widget de réservation de Service partagent le même vocabulaire d’erreurs et ont leurs propres codes, pour les sessions du personnel, les plans de salle, l’inscription en liste d’attente et l’administration des cartes. Aucun n’atteint une clé d’API, et aucun n’est listé ici.

Un code renvoyé par cette API sans entrée sur cette page est un défaut. Signalez-le avec l’identifiant de requête et l’enveloppe complète.

Chaque réponse porte un en-tête X-Request-Id, en cas de succès comme d’échec, et sa valeur désigne cette requête précise dans les journaux de Service. Consignez-la à côté de l’enveloppe reçue : c’est l’identifiant de requête que les paragraphes ci-dessus vous demandent de citer.

Signalez un problème depuis la page de contact de Service, en citant cet identifiant et l’enveloppe complète.