Créer une réservation
POST /api/v1/bookings
Le cœur du produit. Aucune règle métier n’est réécrite ici : disponibilité, battement, horaires d’ouverture, grille tarifaire, devise, fuseau horaire et plafond de l’offre sont tous appliqués par le même service que celui qu’appelle l’application.
La date et l’heure de début sont locales à l’organisation, pas des instants : envoyez
date: "2026-09-12" et startMinute: 540 pour 09:00 en heure locale. Lisez settings.timezone dans
/api/v1/me si vous avez besoin de savoir de quelle heure locale il s’agit.
Autorisation
| Rôle dans l’organisation | Cet endpoint |
|---|---|
owner | Autorisé |
admin | Autorisé |
member | Autorisé |
Le produit donne aux membres le droit de créer des réservations — ce sont souvent eux qui ont le client au téléphone. Les refuser ici rendrait l’API plus stricte que l’écran.
Corps de la requête
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
propertyId | uuid | oui | Une villa active de l’organisation |
customerId | uuid | oui | Un client de l’organisation |
date | chaîne | oui | Jour local YYYY-MM-DD |
startMinute | entier | oui | Minutes depuis minuit local, par exemple 540 pour 09:00 |
durationMinutes | entier | oui | Doit être tarifée pour cette villa |
status | enum | oui | HOLD ou CONFIRMED |
priceAmount | entier | non | Prix négocié ; par défaut, le montant de la grille |
depositPercent | entier | non | De 1 à 100 ; null signifie que le prix complet est dû |
Exemple
curl -X POST -H "x-api-key: $VILLASLOT_API_KEY" -H "content-type: application/json" \
-d '{"propertyId":"1f0a…","customerId":"8d3e…","date":"2026-09-12","startMinute":540,"durationMinutes":120,"status":"HOLD"}' \
"https://villaslot.app/api/v1/bookings"Réponse
201 Created— la réservation. UnHOLDporteholdExpiresAt; unCONFIRMEDnon.
Ce qu’il refuse
-
409 Conflict— la réservation n’a pas été créée, etdetail.reasondit pourquoi :reasonSignification OVERLAPLe créneau, ou son battement, est pris CLOSEDLa villa est fermée ce jour-là OUTSIDE_OPENING_HOURSLe créneau tombe hors de la plage d’ouverture INVALID_INTERVALFin avant le début DURATION_NOT_PRICEDCette durée n’a pas de tarif pour cette villa CURRENCY_NOT_CONFIGUREDL’organisation n’a pas choisi de devise PROPERTY_NOT_BOOKABLEVilla inconnue, archivée, ou d’une autre organisation CUSTOMER_NOT_FOUNDClient inconnu, ou d’une autre organisation -
409 Conflict— la limite de réservations de l’offre est atteinte. -
422 Unprocessable Entity— un champ mal formé ;detailsles liste. -
401 Unauthorized— clé absente ou invalide, ou clé dont le porteur a quitté l’organisation. -
404 Not Found— identifiant inconnu, ou identifiant appartenant à une autre organisation. Les deux sont volontairement indiscernables. -
429 Too Many Requests— plus de 120 requêtes en une minute pour cette clé.
Recette pour un agent
Demandez d’abord les disponibilités, puis créez — un POST refusé coûte un aller-retour et ne vous en dit pas plus
que les créneaux libres ne l’avaient déjà fait :
const {data} = await get(
`/api/v1/properties/${propertyId}/availability?date=2026-09-12&durationMinutes=120`
)
if (data.slots.length === 0) return 'no slot that day'
await post('/api/v1/bookings', {
propertyId,
customerId,
date: '2026-09-12',
startMinute: 540,
durationMinutes: 120,
status: 'HOLD',
})Notes
- Forcer un conflit n’est pas possible par l’API. Forcer une réservation sur un créneau pris est une décision humaine délibérée, prise à l’écran après avoir vu ce avec quoi elle entre en collision.
- Les nuitées ne sont pas encore couvertes par cet endpoint.
Disponibilité Airbnb
Les écritures réussies peuvent renvoyer des warnings génériques avec le code AIRBNB_CONFLICT ou AIRBNB_SYNC_UNAVAILABLE. Une règle bloquante rejette l’opération avec la raison de conflit correspondante ; forceConflict ne peut pas la contourner. Quand la dernière synchronisation réussie date de 65 minutes à 3 heures, les conflits sont encore vérifiés par rapport au dernier instantané et l’avertissement AIRBNB_SYNC_UNAVAILABLE est renvoyé ; au-delà de 3 heures, des données bloquantes entraînent un refus temporaire. Aucune URL de flux, aucun UID ni aucune donnée de voyageur n’est renvoyé.
Plage check-out / check-in
Quand l’organisation protège la plage check-out / check-in (turnoverGuardEnabled), un shooting qui commence avant
l’heure de check-in et finit après l’heure de check-out de la villa, et qui dure moins que
turnoverGuardMinimumMinutes, est créé quand même par l’API, avec l’avertissement
{"code": "TURNOVER_WINDOW", "minimumMinutes": 240}. Le déplacement (POST /api/v1/bookings/{id}/reschedule) le
renvoie aussi dans les warnings de la réservation. Seules les pages publiques refusent ce créneau.
Si, le jour local du shooting, une réservation Airbnb ou une nuitée active de la même villa part ou arrive, le
shooting qui empiète sur la plage est concerné quelle que soit sa durée : il est créé avec l’avertissement
{"code": "TURNOVER_ADJACENT_STAY"}, à la place de TURNOVER_WINDOW.
Un paiement réel est conservé quand la confirmation automatique est empêchée. La réservation garde son statut non confirmé et airbnbResolutionRequiredAt ; le lien client indique qu’une intervention de l’équipe est nécessaire. Rejouer le paiement n’efface jamais ce marqueur. Une confirmation explicite après une nouvelle vérification de disponibilité, ou une annulation explicite, le lève. Aucun remboursement automatique n’est émis.