Aller au contenu

Réserver une salle entière

Un séminaire de soixante personnes qui dispose du salon pour lui seul ne tient pas sur une table. Aucune table ni aucune combinaison n’accueille soixante personnes : une création ordinaire est refusée avec no_table_available, et aucune levée n’atteint ce code. Envoyez plutôt entire_section_id, et la réservation prend toutes les tables de la section, quel que soit le nombre de couverts.

Vous nommez la section. Le plan de salle du restaurant décide quelles tables en font partie, lu au moment où vous réservez, sur le plan de salle en vigueur pour ce service. Une table que le restaurant dessine dans la salle après votre réservation n’en fait pas partie.

Portée Pourquoi ce parcours en a besoin
reservations:read GET /v1/sections, pour trouver la salle.
reservations:write La création et l’annulation.
reservations:write:override entire_section_id sur la création.

La seconde de ces deux portées d’écriture est distincte, car une réservation de salle entière retire toutes les tables de la salle au widget, aux autres intégrations du restaurant et à sa propre attribution des tables. Elle n’implique pas reservations:write, qu’une clé doit porter à côté d’elle. Une clé qui ne la porte pas et envoie entire_section_id est refusée avec 403 insufficient_scope et param: "entire_section_id". Cette vérification a lieu avant la recherche de la section : le refus ne vous dit jamais si l’identifiant existe.

reservations:write:override porte aussi le tableau override et excluded_from_shift_limits. Un restaurant qui accorde à une clé le pouvoir de réserver une salle entière lui accorde ceux-là aussi ; il n’existe pas d’autorisation plus étroite qui s’arrête aux salles.

La portée atteint les sections que le restaurant ne propose pas à la réservation en ligne. Un salon privé est en général bookable_online: false, et le widget ne le propose donc jamais ; une réservation de salle entière le prend malgré tout.

Fenêtre de terminal
curl "https://api.useservice.app/v1/sections" \
-H "Authorization: Bearer $SERVICE_API_KEY"
{
"object": "section",
"id": "sec_7Yd2Qm9",
"name": "Salon",
"area_type": "private_room",
"bookable_online": false,
"group_id": "sec_7Yd2Qm9"
}

Les lignes qui partagent un group_id sont la même salle dessinée sur des plans de salle différents. C’est le group_id que vous passez comme entire_section_id.

Service ne publie aucun chiffre de capacité pour une salle : ni le nombre de places assises, ni le nombre de personnes debout. Un plan de salle compte des chaises, et un nombre de chaises ne sait pas que deux tables de quatre rapprochées accueillent plus ou moins de huit personnes, ni combien de personnes tiennent debout dans la salle une fois les chaises retirées. Combien de personnes y tiennent se règle entre le restaurant et celui qui privatise la salle.

Rien ici ne dit que la salle est libre. Voir Savoir si la salle est libre.

Une salle peut exister et rester hors d’atteinte le soir voulu. Chaque service utilise un plan de salle, une exception de date peut le remplacer, et seules les salles dessinées sur ce plan se réservent entières.

Lisez section_ids sur le service choisi par le client, dans GET /v1/availability pour cette date :

Fenêtre de terminal
curl -G "https://api.useservice.app/v1/availability" \
-H "Authorization: Bearer $SERVICE_API_KEY" \
--data-urlencode "service_date=2026-10-14" \
--data-urlencode "party_size=60"
{
"object": "availability_shift",
"name": "Dîner",
"section_ids": ["sec_7Yd2Qm9", "sec_3Hn6Vc1"]
}

section_ids contient le group_id de chaque salle dessinée sur le plan de salle que ce service utilise à cette date. Rapprochez chacun des lignes de GET /v1/sections qui partagent ce group_id, et passez-le comme entire_section_id. Il ne dépend ni de party_size ni de section_id, et il ignore bookable_online, qu’une réservation de salle entière ne lit pas — si bien qu’un salon privé vendu à la privatisation y figure alors même qu’un client ne peut pas demander à s’y asseoir. Il est vide quand le plan en vigueur n’a aucune section.

Une salle absente de section_ids est refusée avec no_table_available_in_section pour ce service à cette date. Une salle qui y figure n’est pas une salle libre : une autre réservation peut tenir l’une de ses tables, et vous ne l’apprenez qu’en tentant la réservation.

Décidez des salles à proposer en privatisation à partir de section_ids seul.

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-14",
"start_time": "19:30",
"party_size": 60,
"entire_section_id": "sec_7Yd2Qm9",
"duration": "until_shift_end",
"guest": { "first_name": "Marie", "last_name": "Dupont", "email": "[email protected]" },
"guest_notes": "Séminaire — cocktail debout, vidéoprojecteur"
}'

Le 201 est une réservation ordinaire, et son tableau tables liste toutes les tables de la salle. entire_section_id n’est pas un section_id renforcé : section_id demande une table quelque part dans une section, et envoyer les deux donne un 400.

Aucun plafond ne lui est appliqué. party_size n’est pas mesuré à la salle et le max_party_size de GET /v1/configuration, auquel une création ordinaire est tenue, ne s’applique pas ici : party_size_too_large ne survient pas sur une réservation de salle entière. Un groupe de soixante dans une salle dont les tables accueillent quarante personnes est accepté sans override, car une réception debout dans une salle vidée de ses tables échappe au plan de salle. C’est le restaurant qui sait ce que son salon accueille et il a convenu de la privatisation avant votre envoi.

Un seul contrôle subsiste, et c’est la borne inférieure. Un groupe en dessous du minimum du restaurant reste refusé avec party_size_too_small, levable comme il l’est sur toute réservation. Les plafonds de couverts du service n’encadrent pas non plus une privatisation : soixante sur un service où il reste quarante couverts est réservé et non refusé. Voir Plafonds du service.

Sans duration, la réservation dure ce que les règles de réservation du restaurant accordent à un groupe de cette taille, comme toute réservation. duration: "until_shift_end" occupe la salle jusqu’à la fin du service. C’est la seule valeur que duration accepte, duration n’est accepté qu’à côté de entire_section_id, et aucun champ ne permet d’envoyer une heure de fin.

until_shift_end déplace la fin, pas le début. start_time reste l’un des horaires d’arrivée du service, tels que GET /v1/availability les liste : 19:10 dans un service qui accueille toutes les demi-heures est refusé avec start_time_off_grid, alors que le 19:30 ci-dessus est accepté.

Une réservation de salle entière est exclue par défaut des plafonds de couverts du service : l’événement du salon ne compte pas dans les couverts de la salle principale. Envoyez excluded_from_shift_limits: false pour la compter. Aucune des deux écritures n’est refusée à une clé qui pouvait envoyer entire_section_id : la même portée porte les deux.

L’exclusion est ce qui permet à un service déjà en vente d’accorder la salle : les couverts restants ne sont pas mesurés à la privatisation, comme l’explique Un séminaire demande un seul champ. Envoyez excluded_from_shift_limits: false et ils le sont, comme sur toute autre réservation. Les autres règles de réservation (délai de clôture, fenêtre d’anticipation, créneau bloqué) sont vérifiées comme sur toute création, et les levables se lèvent avec override.

Si une réservation occupe l’une des tables de la salle pendant la plage que cette réservation occuperait, la création est refusée et rien n’est placé. Une réservation de salle entière n’est jamais placée sur une partie de la salle.

{
"error": {
"type": "invalid_request_error",
"code": "booking_rules_violated",
"violations": [
{
"code": "section_occupied",
"message": "…",
"param": "entire_section_id",
"overridable": false,
"conflicts": [
{ "table": "tbl_4Rk8Wz2", "start_time": "19:00", "end_time": "21:00" }
]
}
]
}
}

Chaque entrée de conflicts est une table occupée et l’heure à partir de laquelle et jusqu’à laquelle elle est prise. Les entrées sont triées par table, puis par heure de début : deux refus ne diffèrent que si la salle a changé. table vaut null lorsque la salle n’a aucune table et qu’une autre réservation de salle entière l’occupe. Rien dans une entrée ne dit qui occupe la table : ni client, ni identifiant de réservation, ni nombre de couverts.

La plage est celle que cette réservation occuperait. Avec until_shift_end, un groupe installé à 22:30 gêne ; sans lui, une réservation que les règles terminent à 22:00 ne croise pas ce groupe.

section_occupied n’est pas levable, et le nommer dans override donne un 400. Les remèdes sont une autre heure ou une autre date, ou le restaurant qui déplace les réservations listées. Le restaurant les voit sur son propre plan de salle.

Une section qui existe mais n’est pas dessinée sur le plan de salle en vigueur pour ce service (une terrasse absente du plan d’hiver) est refusée avec no_table_available_in_section, param: "entire_section_id". section_ids sur le service vous le dit avant la tentative, comme l’explique Les salles que propose chaque service.

Aucun point de terminaison ne répond à cette question. GET /v1/availability indique quelle table convient à un groupe : pour un groupe de soixante, il n’a aucun créneau à proposer, et son section_ids dit dans quelles salles le service se réserve entier, pas lesquelles sont prises. Tentez la réservation : un refus section_occupied et ses conflicts sont la réponse, et une tentative refusée ne réserve rien.

  • Elle n’est pas modifiable. PATCH /v1/reservations/{id} sur une réservation de salle entière répond 409 section_booking_not_modifiable, pour toute modification, y compris guest_notes seul. (Un corps vide, ou qui nomme un champ qu’aucun PATCH n’accepte, reçoit d’abord son 400.) Pour la changer, annulez et réservez de nouveau.
  • L’annulation rend toutes les tables. POST /v1/reservations/{id}/cancel fonctionne comme sur toute réservation.
  • Elle ne peut pas faire l’objet d’une option. POST /v1/holds portant entire_section_id ou duration donne un 400 qui nomme le champ. Réservez la salle directement.
  • Une réservation ordinaire ne peut pas le devenir. entire_section_id ou duration sur un PATCH de n’importe quelle réservation donne un 400 qui nomme le champ.

L’événement reservation.created enregistre la demande, et vous pouvez le relire sur GET /v1/reservations/{id}/events :

{
"object": "reservation_event",
"type": "reservation.created",
"data": {
"source": "api",
"party_size": 60,
"excluded_from_shift_limits": true,
"booked_entire_section": "sec_7Yd2Qm9",
"duration": "until_shift_end"
}
}

booked_entire_section est la salle telle que dessinée sur le plan de salle de ce soir-là. duration n’est présent que si vous en avez demandé une.

Code Où il arrive Que faire
insufficient_scope, param: "entire_section_id" 403 Demandez au restaurant une clé qui porte reservations:write:override.
bad_request 400 section_id ou hold_id à côté de entire_section_id, duration sans lui, ou une duration autre que until_shift_end. param vaut entire_section_id ou duration.
section_occupied dans violations, overridable: false Lisez conflicts. Une autre heure ou une autre date, ou le restaurant déplace quelqu’un.
no_table_available_in_section dans violations, overridable: false La salle n’est pas sur le plan de salle de ce service. Choisissez parmi section_ids sur le service dans GET /v1/availability.
section_booking_not_modifiable 409 sur PATCH Annulez et réservez de nouveau.