API RiLoBook — v1

API REST complète pour bâtir des intégrations à RiLoBook : POS, CRM, agenda, BI, kiosk, app mobile. Réservations bidirectionnelles, gestion clients, plan de salle, pointage, paiements, et webhooks sortants signés pour la synchronisation temps réel.

Sommaire

Authentification

Tous les endpoints /api/v1/* exigent un jeton Bearer. Créez-en un depuis Paramètres → API (rôle owner requis). Le jeton plaintext est affiché une seule fois.

Format : rlb_…. En-tête :

Authorization: Bearer rlb_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Ou X-Api-Key: rlb_… si votre client ne supporte pas Bearer.

Un jeton est lié à un restaurant + à l'utilisateur qui l'a créé. Ses permissions sont l'intersection de (a) celles de l'utilisateur et (b) les scopes sélectionnés à la création. Stockez-le dans un gestionnaire de secrets — jamais en clair dans Git.

Scopes disponibles

ScopeAutorise
bookings.readLecture réservations, disponibilités
bookings.writeCréation / modification / annulation réservations
waitlist.read / waitlist.writeListe d'attente
guests.read / guests.writeFichier clients
settings.read / settings.writeParamètres restaurant + services
employees.read / employees.writeÉquipe + planning + pointage
payments.readHistorique paiements Stripe
webhooks.read / webhooks.writeAbonnements webhooks sortants
audit.readJournal d'audit

Base URL

https://votre-instance.rilobook.app/api/v1

Erreurs

Réponses JSON, codes HTTP standard. Corps :

{ "code": "unauthenticated", "message": "invalid_token", "meta": {} }
HTTPcodeQuand
400bad_requestChamp manquant ou invalide
401unauthenticatedJeton absent, invalide, révoqué
403forbidden / insufficient_scopePermission manquante
404not_foundRessource inexistante
409conflict / capacity_exhausted / immutable_field / idempotency_mismatchConflit d'état
429rate_limitedLimite dépassée
500internal_errorBug serveur — à signaler

Rate limiting

120 requêtes par minute par jeton (bucket). Dépassement → 429. Backoff exponentiel recommandé.

Idempotence

Sur POST / PATCH de mutations, envoyez un en-tête Idempotency-Key (UUID ou hash métier, max 120 chars). Le serveur cache la réponse 24 h ; rejouer la même requête renvoie la même réponse avec l'en-tête Idempotent-Replay: true.

Si la même clé est utilisée avec un body différent → 409 idempotency_mismatch.

POST /api/v1/reservations
Authorization: Bearer $RLB_TOKEN
Idempotency-Key: 7e1c1a31-…
Content-Type: application/json

{ "servicePeriodId": "…", "serviceDate": "2026-06-12", ... }

Pagination cursor

Les listes utilisent un curseur opaque. Réponse :

{ "reservations": [...], "nextCursor": "MjAyNi0w…", "hasMore": true }

Page suivante : ?cursor=MjAyNi0w…. Paramètre limit (1–200, défaut 50).

Webhooks sortants

Abonnez une URL HTTPS à un sous-ensemble d'événements. Chaque déclenchement POST le JSON suivant à votre endpoint :

{
  "id": "<event UUID>",
  "type": "reservation.confirmed",
  "createdAt": "2026-05-28T18:01:23Z",
  "restaurantId": "<your restaurant id>",
  "data": { ...resource object... }
}

En-têtes envoyés

X-RiLoBook-Event:     reservation.confirmed
X-RiLoBook-Delivery:  <delivery UUID>
X-RiLoBook-Signature: t=1748448083,v1=<hex HMAC-SHA256>
User-Agent:           RiLoBook-Webhooks/1.0
Content-Type:         application/json

Vérifier la signature (Node.js)

import crypto from 'node:crypto';

function verify(rawBody, headerSig, secret, toleranceSec = 300) {
  const m = /t=(\d+),v1=([0-9a-f]+)/.exec(headerSig || '');
  if (!m) return false;
  const [, t, sig] = m;
  if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSec) return false;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

Événements supportés

TypePayload data
reservation.createdReservation
reservation.confirmedReservation
reservation.updatedReservation
reservation.cancelledReservation
reservation.no_showReservation
waitlist.added / waitlist.removedWaitlistEntry
guest.created / guest.updatedGuest
ping{ message: 'test delivery' }

Retry & livraisons

Si votre endpoint ne renvoie pas un 2xx en moins de 8 s, la livraison est ré-essayée selon un backoff exponentiel (1 s, 5 s, 25 s, 125 s, 625 s, 3125 s, 6 h, 6 h) — maximum 8 tentatives. Après échec final → status dead. Inspectez les livraisons via GET /api/v1/webhooks/{id}/deliveries.

GET /me

GET /api/v1/me

Healthcheck — renvoie le contexte du jeton.

{
  "userId": "…", "restaurantId": "…",
  "fullName": "Loris Dupont", "email": "loris@example.com",
  "role": "owner",
  "permissions": ["bookings.read", "bookings.write", "…"]
}

Restaurant & services

GET /api/v1/restaurant settings.read
PATCH /api/v1/restaurant settings.write

Champs PATCH : name, timezone, locale, capacityTotal.

GET /api/v1/service-periods settings.read
POST /api/v1/service-periods settings.write
GET /api/v1/service-periods/{id}
PATCH /api/v1/service-periods/{id}
DELETE /api/v1/service-periods/{id}

Corps POST :

{ "name": "Dîner", "dayOfWeek": 5, "startTime": "19:00",
  "endTime": "23:00", "defaultDiningDurationMinutes": 90,
  "maxCoversPerSlot": 16, "depositRequired": false, "isActive": true }

Disponibilités

GET /api/v1/availability?date=YYYY-MM-DD&partySize=N bookings.read
{ "date": "2026-06-12", "partySize": 4,
  "slots": [ { "servicePeriodId": "…", "servicePeriodName": "Dîner",
               "slotTime": "19:00:00", "available": true }, ... ] }

Réservations

GET /api/v1/reservations bookings.read

Filtres : date, from, to (range), status, source, guestId, updatedSince (sync incrémental). Pagination cursor.

GET /api/v1/reservations/{id}
POST /api/v1/reservations bookings.write

Crée et confirme immédiatement (pas de checkout Stripe — réservé au widget public). Mêmes verrous de capacité que le dashboard.

{
  "servicePeriodId": "…",
  "serviceDate": "2026-06-12",
  "slotTime": "19:30:00",
  "partySize": 4,
  "channel": "phone",
  "skipConfirm": false,
  "guest": {
    "fullName": "Marie Curie",
    "email": "marie@example.com",
    "phone": "+33611223344",
    "specialRequests": "Anniversaire"
  }
}
PATCH /api/v1/reservations/{id}

Combine actions de transition + mises à jour. actionconfirm | cancel | no_show. Champs modifiables : specialRequests, channel, guestId (swap), guest (édite le client lié).

partySize, slotTime, serviceDate, servicePeriodId sont immuables — annulez et recréez (réservé pour préserver les verrous de capacité).
DELETE /api/v1/reservations/{id}

Alias de { "action": "cancel" }.

Clients (guests) CRUD

GET /api/v1/guests guests.read

Filtres : q, hasEmail=1, hasPhone=1. Pagination cursor.

GET /api/v1/guests/{id}
POST /api/v1/guests guests.write
PATCH /api/v1/guests/{id}
DELETE /api/v1/guests/{id}
{ "fullName": "Marie Curie", "email": "marie@example.com",
  "phone": "+33611223344", "notes": "VIP", "tags": ["vip","gluten-free"] }

Liste d'attente CRUD

GET /api/v1/waitlist waitlist.read
POST /api/v1/waitlist waitlist.write
PATCH /api/v1/waitlist/{id}
DELETE /api/v1/waitlist/{id}

Employés

GET /api/v1/employees employees.read
POST /api/v1/employees employees.write
PATCH /api/v1/employees/{id}
DELETE /api/v1/employees/{id}

Plan de salle & tables

GET /api/v1/floors settings.read
GET /api/v1/tables?floorId=… settings.read
L'édition du plan (drag & drop, création tables/floors) reste exclusivement dans le dashboard pour éviter les conflits avec les assignations live.

Pointage & planning

GET /api/v1/timesheet?from=YYYY-MM-DD&to=YYYY-MM-DD&userId=… employees.read
GET /api/v1/shifts?from=…&to=… employees.read

Paiements

GET /api/v1/payments payments.read
GET /api/v1/payments/{id}

Filtres : reservationId, status. Pagination cursor.

Audit logs

GET /api/v1/audit-logs audit.read

Filtres : entityType, entityId, action. Pagination cursor.

Endpoints Webhooks

GET /api/v1/webhooks webhooks.read
POST /api/v1/webhooks webhooks.write
GET /api/v1/webhooks/{id}
PATCH /api/v1/webhooks/{id}
DELETE /api/v1/webhooks/{id}
POST /api/v1/webhooks/{id}/test
GET /api/v1/webhooks/{id}/deliveries
POST /api/v1/webhooks
{
  "url": "https://votre-backend/webhooks/rilobook",
  "events": ["reservation.created","reservation.confirmed","reservation.cancelled"],
  "description": "Sync POS"
}

La réponse contient secret (à conserver pour vérifier la signature) — affiché uniquement à la création.

Exemples complets

Node.js — synchroniser les réservations modifiées

const TOKEN = process.env.RLB_TOKEN;
const BASE  = 'https://votre-instance.rilobook.app/api/v1';

async function syncSince(isoDate) {
  let cursor = null, page = 0;
  do {
    const url = new URL(`${BASE}/reservations`);
    url.searchParams.set('updatedSince', isoDate);
    url.searchParams.set('limit', '100');
    if (cursor) url.searchParams.set('cursor', cursor);
    const res = await fetch(url, { headers: { Authorization: `Bearer ${TOKEN}` } });
    if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
    const data = await res.json();
    for (const r of data.reservations) await upsertToPOS(r);
    cursor = data.nextCursor;
    if (++page > 200) throw new Error('cursor loop guard');
  } while (cursor);
}

Python — créer une réservation avec idempotency

import os, uuid, requests

TOKEN = os.environ["RLB_TOKEN"]
BASE  = "https://votre-instance.rilobook.app/api/v1"
H = {
    "Authorization": f"Bearer {TOKEN}",
    "Idempotency-Key": str(uuid.uuid4()),
}

r = requests.post(f"{BASE}/reservations", headers=H, json={
    "servicePeriodId": "…",
    "serviceDate": "2026-06-12",
    "slotTime": "19:30:00",
    "partySize": 4,
    "channel": "phone",
    "guest": {"fullName": "Marie Curie", "phone": "+33611223344"},
})
r.raise_for_status()
print(r.json()["referenceCode"])

cURL — souscrire à un webhook

curl -X POST https://votre-instance/api/v1/webhooks \
  -H "Authorization: Bearer $RLB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://votre-backend/webhooks/rilobook",
    "events": ["reservation.created","reservation.confirmed","reservation.cancelled"]
  }'