Aller au contenu

Authentification

L’API Service authentifie les requêtes avec une clé d’API, envoyée comme jeton Bearer dans l’en-tête Authorization.

GET /v1/reservations HTTP/1.1
Host: api.useservice.app
Authorization: Bearer sk_live_YOUR_SECRET_KEY

Avec curl :

Fenêtre de terminal
curl https://api.useservice.app/v1/reservations \
-H "Authorization: Bearer $SERVICE_API_KEY"

Les exemples de cette documentation lisent la clé depuis une variable d’environnement SERVICE_API_KEY plutôt que de la coder en dur — gardez votre vraie clé hors de l’historique du shell, des scripts et du contrôle de version.

  • Une clé appartient à un seul restaurant. Elle authentifie ce restaurant et ne renvoie jamais que les données de ce restaurant, ce qui explique qu’aucune requête ne porte d’identifiant de restaurant. Il n’existe pas de clé de groupe : un groupe de dix établissements détient dix clés, une par établissement, et chaque appel concerne un seul établissement.
  • Les clés sont secrètes. Une clé commençant par sk_live_ porte les portées que le restaurant lui a accordées, ce qui peut inclure réserver, modifier et annuler pour le compte de ce restaurant. Traitez-la comme un mot de passe.
  • Les clés sont réservées au serveur. N’intégrez jamais une clé dans un navigateur, une application mobile, un dépôt public ou tout code côté client. Utilisez-la uniquement depuis votre backend — voir Aucune clé utilisable dans un navigateur.
  • Les clés ne sont affichées qu’une fois. Le secret complet est affiché une seule fois, à la création de la clé. Seul un court suffixe (les 4 derniers caractères) est conservé et affiché par la suite, alors stockez la valeur complète de manière sécurisée dès la création. Si vous la perdez, créez une nouvelle clé.
  • Plusieurs clés sont prises en charge. Un restaurant peut détenir plusieurs clés (par exemple, une par intégration), ce qui vous permet d’en faire tourner ou d’en révoquer une sans perturber les autres.

Les clés d’API sont en libre-service depuis le back-office. Un propriétaire ou copropriétaire peut les gérer dans Paramètres → Développeurs :

  1. Ouvrez Paramètres → Développeurs et créez une nouvelle clé d’API.
  2. Choisissez les portées qu’elle doit porter. Accordez le minimum dont l’intégration a besoin ; vous ne pourrez pas en ajouter ensuite.
  3. Copiez le secret (sk_live_…) — il n’est affiché qu’une fois, à la création. Stockez-le en lieu sûr avant de quitter la page.
  4. Envoyez-le comme jeton Bearer, comme indiqué ci-dessus.

Chaque clé est épinglée à une version de l’API à sa création et peut être révoquée à tout moment depuis le même écran. Un restaurant peut détenir jusqu’à 10 clés — gardez-en une par intégration pour pouvoir les faire tourner ou les révoquer individuellement.

Il n’y a pas d’environnement de bac à sable ou de test distinct : toutes les clés sont en production (sk_live_…) et agissent sur le vrai restaurant via l’unique URL de base de production. Une réservation que vous créez est une réservation pour laquelle le restaurant dressera une table, et une annulation parvient au client. Répétez sur un restaurant qui vous appartient.

Une clé ne porte pas automatiquement toutes les permissions du restaurant. Chacune est créée avec une liste de portées, choisie à la création, et elle ne peut pas l’élargir ensuite — pour accorder davantage, créez une deuxième clé. Une clé créée avant l’existence des portées porte reservations:read, et c’est aussi la valeur par défaut d’une nouvelle clé.

Portée Ce qu’une clé qui la porte peut faire
reservations:read Lire les réservations et leurs événements de cycle de vie, les disponibilités, la politique de réservation, les sections, les options et la file d’attente.
reservations:write Réserver, modifier et annuler. Également poser et rendre des options : il n’existe pas de portée distincte pour les options, car une clé qui peut réserver peut poser une option.
reservations:write:override Trois choses, sur une seule portée. Envoyer override, qui réserve en passant outre une règle fixée par le restaurant. Envoyer excluded_from_shift_limits: true, qui exclut définitivement une réservation du plafond de couverts du service. Envoyer entire_section_id, qui réserve toutes les tables d’une section, y compris une section que le restaurant ne propose pas à la réservation en ligne. Voir Réserver une salle entière.
guests:read Lire les fiches clients, déplier un client depuis une réservation, déplier le jeton de modification sur POST /v1/reservations, et lire les données client d’une réservation — sur toutes les réservations, y compris celles que votre clé a créées. Un jeton de modification ouvre la page de réservation du client en son nom, étape carte comprise : traitez-le comme son mot de passe.

Les portées ne s’emboîtent pas. reservations:write n’implique pas reservations:read, et reservations:write:override n’implique pas reservations:write — demandez chacune de celles que l’intégration utilise. reservations:write:override exige reservations:write à côté d’elle, et en est délibérément séparée : elle permet à un restaurant de vous confier la possibilité de réserver sans vous confier celle de passer outre ses propres règles, d’exclure une réservation de ses plafonds de couverts ou de retirer une salle de la vente.

Ces trois pouvoirs étaient trois portées jusqu’au 22 septembre 2026. Ils n’en font plus qu’une. Les trois champs restent distincts — chacun est refusé séparément, en nommant son propre param — mais un restaurant qui en accorde un dit une seule phrase, et une clé qui porte la portée les porte tous les trois.

Certaines portées sont exigées par un champ, pas par un point de terminaison

Section intitulée « Certaines portées sont exigées par un champ, pas par un point de terminaison »

reservations:write:override et guests:read peuvent être exigées par quelque chose à l’intérieur de la requête plutôt que par le point de terminaison appelé. Sur une création, un tableau override, excluded_from_shift_limits: true et entire_section_id exigent chacun la première ; expand=guest exige la seconde, depuis n’importe quel point de terminaison.

Le refus vous dit lequel des deux cas s’est produit. Un 403 insufficient_scope qui nomme un param signifie que l’appel est à votre portée et que seul ce champ ne l’est pas : retirer le champ et réessayer mérite d’être automatisé. Sans param, la clé n’atteint pas du tout le point de terminaison et réessayer n’y changera rien.

Erreurs est la référence : la portée exigée par chaque point de terminaison, les champs qui en exigent une, et l’enveloppe du refus.

Une réservation porte les coordonnées données pour cette réservation (contact_email, contact_phone), la demande du client lui-même (guest_notes) et la note privée du restaurant à son sujet (internal_notes). Ces quatre champs sont des données client, et guests:read est la seule chose qui les lit. Une clé qui la porte les lit sur toutes les réservations, tels qu’ils sont, si bien qu’une modification ultérieure par l’équipe du restaurant, ou par le client depuis sa page de réservation, lui parvient aussi.

Une clé qui ne porte pas la portée n’en lit aucun, sur aucune réservation — y compris celles qu’elle a créées elle-même, et la réponse à la création qui les a faites. Vous renvoyer ce que vous venez d’envoyer ne vous apprendrait rien ; relire la même réservation une heure plus tard, si, et c’est pourquoi vos propres réservations ne font pas exception.

Là où ils sont retenus, les quatre champs sont absents de la réponse, pas null. null a déjà un sens sur ces champs : contact_email: null indique que la réservation suit les coordonnées de la fiche client. Vérifiez la présence de la clé avant de lire sa valeur. Rien n’est refusé : aucun 403, et le reste de la réservation est complet.

La règle vaut partout où une réservation est rendue : une liste, une lecture unitaire, la réponse à une création, à un PATCH ou à une annulation, et les changes des événements d’une réservation. Un PATCH envoyé par une clé sans guests:read écrit guest_notes et internal_notes et répond sans eux. Les notes d’une entrée de file d’attente, la demande du client lui-même, relèvent de la même règle.

internal_notes est la note du restaurant lui-même et non les mots du client, et vous l’écrivez autant que vous la lisez : c’est un champ de la création et du PATCH, la note que l’équipe du restaurant saisit sur son propre formulaire de réservation. La note privée d’une entrée de file d’attente ne figure sur aucune surface partenaire.

Les notes d’une fiche client sont un autre champ : les notes que l’équipe du restaurant tient sur la fiche du client. Le client ne les écrit pas. Elles vous parviennent partout où la fiche client vous parvient : GET /v1/guests, expand=guest et les événements guest.*.

Un point de terminaison de webhook n’est lié à aucune clé, et ses livraisons portent les données client en entier. Voir Les livraisons portent les données client.

Toutes les informations d’authentification émises par cette API sont secrètes. Il n’existe aucune variante publiable, et api.useservice.app ne répond à aucune requête d’origine croisée venant d’une origine que vous contrôlez : une clé ne peut pas fonctionner depuis une page que vous servez à des clients, même si vous acceptiez de l’y exposer.

Un tunnel de réservation que vous construisez vous-même a donc besoin de votre propre serveur : le navigateur du client parle à votre backend, et seul votre backend détient la clé. Pour un parcours de réservation qui tourne entièrement dans le navigateur, sans backend, utilisez plutôt le widget de réservation — il ne demande aucune clé.

Une clé manquante, mal formée, révoquée ou inconnue renvoie 401 Unauthorized avec une enveloppe d’erreur. Il existe deux codes d’authentification :

  • auth_header_missing — aucun en-tête Authorization: Bearer … n’a été envoyé.
  • invalid_token — une clé a été envoyée mais elle est invalide, révoquée, expirée ou inconnue.
{
"error": {
"type": "authentication_error",
"code": "invalid_token",
"message": "The provided API key is invalid, revoked, or expired.",
"doc_url": "https://docs.useservice.app/api/errors#invalid_token"
}
}

(param est omis ici — aucun paramètre de requête n’est en cause.)