BOOKING API v1.0

4U Booking API

Documentation complète de l'API de gestion des réservations. Créer, modifier, consulter, check-in et gérer les paiements des bookings.

Base: /bookings
Payments: /booking-payments
Auth: x-api-key
Format: JSON
← Retour à la doc principale

Sommaire

🔐 Authentification

Clé API requise

Toutes les requêtes aux endpoints /bookings et /booking-payments nécessitent une clé API valide.

curl -H "x-api-key: YOUR_API_KEY" https://listings.4u-realestate.org/bookings

🔄 Cycle de vie d'un booking

Booked → Check-in → Checked → Validation →

À tout moment :
Cancelled Declined | No-show | Soft delete
Booked
Réservation confirmée
Checked
Check-in effectué
Paid
Paiements validés
Cancelled
Annulée
Declined
Refusée
Stand by
En attente
Open
Lodgify Open
Tentative
Lodgify Tentative

💰 Calcul financier

Les calculs utilisent la configuration de la collection BookingSource pour chaque plateforme.

1. taxRate = BookingSource.tax / 100
2. taxes = totalAmount × taxRate / (1 + taxRate) ← tax-included
3. subtotal = totalAmount - taxes - cleaningFees
4. platformFees = (subtotal + cleaningFees) × platformFees% / 100
Manual/OH : totalAmount × platformFees% (défaut 5%)
5. bankFees = totalAmount × bankFees% / 100 (Manual/OH: défaut 4%)
6. rentalIncome = totalAmount - cleaningFees - guestInsurance - taxes - platformFees - bankFees
7. managementFees = max(rentalIncome, 0) × 25%
8. netIncome = max(rentalIncome, 0) - managementFees
Note : Si totalAmount = 0 (owner booking), guestInsurance = 0. Le guestInsurance par défaut est 28.99 USD.

🚫 Dates bloquées manuellement (Lodgify)

En plus des réservations, un lot peut être rendu indisponible manuellement sur Lodgify pour ne pas recevoir de bookings. Il existe deux types de blocages, exposés différemment dans l'API :

Type de blocageOù le trouverComment le reconnaître
Réservation manuelle "Owner blocked" GET /bookings, GET /lots/:id/availability (tableau bookings) bookingSource: "Manual" et totalAmount: 0 (flag isManualBlock: true dans /lots/:id/availability). Nom client de type "Owner 203", "LONG TERM - BLOCKED PERIOD"…
Blocage pur du calendrier (closed period) GET /bookings?includeBlockedPeriods=true, GET /lots/:id/availability (tableau blockedPeriods) Objet { start, end, type, lodgifyPeriodId }. Ce n'est pas un booking : aucune fiche client, aucun montant.
Synchronisation : les closed periods sont récupérées depuis Lodgify toutes les heures (plage -12 mois → +18 mois). Le champ blockedPeriodsSyncedAt indique la date de la dernière synchro. Un blocage retiré sur Lodgify disparaît de l'API à la synchro suivante.
Format des dates : start et end sont au format YYYY-MM-DD et représentent la première et la dernière nuit bloquées (inclusives). Ex: start: "2026-10-11", end: "2026-10-12" = nuits du 11 et du 12 octobre bloquées, le lot est de nouveau disponible à partir du 13. À la différence des bookings où departure est le jour du départ.
ChampTypeDescription
startYYYY-MM-DDPremière nuit bloquée (inclusif)
endYYYY-MM-DDDernière nuit bloquée (inclusif)
typestringclosed_period = blocage manuel du calendrier Lodgify · unavailable = indisponible sans booking ni closed period (ex: calendrier externe/iCal)
lodgifyPeriodIdnumber | nullID de la closed period côté Lodgify (null pour unavailable)
syncedAtISO dateDernière fois que la période a été vue sur Lodgify
lotId, lotName, lotNo, lodgifyId—Lot concerné (uniquement dans GET /bookings, où plusieurs lots sont mélangés)

Exemple : construire le calendrier complet d'un lot

const res = await fetch(`${BASE}/lots/${lotId}/availability?start=2026-10-01&end=2026-12-31`, { headers });
const { bookings, blockedPeriods } = await res.json();

// Nuits occupées par des réservations (arrival inclus, departure exclu)
const busy = bookings.map(b => ({ from: b.arrival.slice(0, 10), to: b.departure.slice(0, 10), reason: b.isManualBlock ? 'owner-block' : 'booking' }));

// Nuits bloquées manuellement (start et end inclus)
const blocked = blockedPeriods.map(p => ({ from: p.start, to: p.end, reason: p.type }));

📡 Endpoints Bookings

▶ GET /bookings Lister les bookings avec filtres

Liste paginée des bookings avec filtres, tri et résumé des paiements.

QUERY PARAMETERS

ParamTypeDescription
pagenumberPage (défaut: 1)
limitnumberRésultats/page (défaut: 20, max: 100)
lotIdObjectIdFiltrer par lot
lotIdsstringIDs séparés par virgules
statusstringEx: "Booked,Checked" (séparés par virgules)
bookingSourcestringEx: "AirbnbIntegration"
bookingNonumberRecherche exacte
tenantNamestringRecherche regex insensible casse
startDateISO datearrival ≥ startDate
endDateISO datearrival ≤ endDate
sortstringarrival_asc, arrival_desc, newest, oldest
includeBlockedPeriodsbooleanAjoute les dates bloquées manuellement sur Lodgify (synchronisées chaque heure). Lots = lotId/lotIds si fournis, sinon tous. Plage = startDate/endDate (défaut: aujourd'hui → +12 mois, max 24 mois)

Réponse

{
  "success": true,
  "bookings": [{
    "_id": "...", "bookingNo": 1234567,
    "lotId": { "_id": "...", "name": "Villa Ocean", "lotNo": "A-101" },
    "status": "Booked", "arrival": "2025-06-01", "departure": "2025-06-07",
    "totalAmount": 1500, "cleaningFees": 100, "taxes": 71.43,
    "tenantInformations": { "name": "John Doe", "email": "john@example.com" },
    "paymentsSummary": { "totalPaid": 750, "count": 1, "remaining": 750, "isFullyPaid": false }
  }],
  "total": 42, "page": 1, "limit": 20, "totalPages": 3,
  "blockedPeriodsRange": { "start": "2026-09-26", "end": "2027-09-26" },   // si includeBlockedPeriods=true
  "blockedPeriodsSyncedAt": "2026-09-26T10:05:12.000Z",                   // dernière synchro Lodgify
  "blockedPeriods": [{
    "lotId": "...", "lotName": "B-203", "lotNo": "A-101", "lodgifyId": "675031",
    "start": "2026-11-03", "end": "2026-11-09",          // nuits bloquées, dates inclusives
    "type": "closed_period", "lodgifyPeriodId": 4917894, "syncedAt": "2026-09-26T10:05:12.000Z"
  }]
}

Note : les blocages faits via une réservation manuelle "Owner blocked" sur Lodgify remontent déjà comme bookings (bookingSource: "Manual", totalAmount: 0). blockedPeriods ne contient que les blocages purs du calendrier Lodgify (closed_period), synchronisés toutes les heures depuis Lodgify (plage aujourd'hui → +18 mois).

▶ GET /lots/:id/availability Calendrier d'un lot : bookings + dates bloquées Lodgify

Bookings actifs (Booked, Checked, Paid, Stand by) chevauchant la plage + périodes bloquées manuellement sur Lodgify (synchronisées chaque heure).

ParamTypeDescription
startYYYY-MM-DDDébut de plage (défaut: aujourd'hui)
endYYYY-MM-DDFin de plage (défaut: start + 12 mois, max 24 mois)

Réponse

{
  "success": true,
  "lot": { "id": "...", "name": "B-203", "lotNo": "SXM CB 097/2022 A-55", "lodgifyId": "675031" },
  "range": { "start": "2026-09-26", "end": "2027-09-26" },
  "blockedPeriodsSyncedAt": "2026-09-26T10:05:12.000Z",
  "bookings": [{
    "_id": "...", "bookingNo": 23108984, "arrival": "2026-10-01T15:00:00.000Z", "departure": "2026-10-03T11:00:00.000Z",
    "status": "Booked", "bookingSource": "Manual", "isManualBlock": true, "guestName": "Owner 203"
  }],
  "blockedPeriods": [{
    "start": "2026-11-03", "end": "2026-11-09", "type": "closed_period", "lodgifyPeriodId": 4917894,
    "syncedAt": "2026-09-26T10:05:12.000Z"
  }]
}
▶ GET /bookings/upcoming Bookings à venir

Bookings dont le départ est ≥ maintenant. Exclut Cancelled, Declined, Open, Tentative.

ParamTypeDescription
lotIdsstringIDs séparés par virgules
pagenumberDéfaut: 1
limitnumberDéfaut: 20
sortBystringChamp de tri (défaut: arrival)
sortOrderstringasc ou desc
▶ GET /bookings/accounting Vue comptable avec totaux

Vue comptable avec agrégation des totaux financiers et liste des sources.

ParamTypeDescription
lotIdsstringIDs séparés par virgules
statusstringFiltrer par statut(s)
bookingSourcestringRegex insensible casse
bookingNonumberNuméro exact
startDateISO datearrival ≥
endDateISO datearrival ≤
page, limitnumberPagination (limit défaut: 50)
sortBy, sortOrderstringTri (défaut: arrival desc)

Réponse

{
  "success": true,
  "bookings": [...],
  "totalCount": 150, "totalPages": 3, "currentPage": 1,
  "totals": {
    "totalAmount": 125000, "totalSubtotal": 110000,
    "totalPrepaidAmount": 100000, "totalCleaningFees": 8000,
    "totalTaxes": 5952, "totalPlatformFees": 3750,
    "totalBankFees": 5000, "totalRentalIncome": 95000,
    "bookingCount": 150
  },
  "bookingSources": ["AirbnbIntegration", "BookingCom", "Manual"]
}
▶ GET /bookings/services Services add-on disponibles

Liste des services add-on actifs proposés au check-in.

Réponse

{
  "success": true, "total": 5,
  "services": [{
    "_id": "...", "title": "Early Check-in",
    "description": "Check-in before 3 PM",
    "price": 50, "originalPrice": 75,
    "image": "...", "displayOrder": 1
  }]
}
▶ GET /bookings/sources Sources & configuration fees

Liste des sources de booking avec leurs pourcentages de frais.

Réponse

{
  "success": true, "total": 6,
  "sources": [{
    "source": "AirbnbIntegration",
    "platformFees": 3, "bankFees": 0,
    "tax": 5, "guestInsurance": 28.99
  }]
}
▶ GET /bookings/by-number/:bookingNo Trouver par numéro
ParamTypeDescription
bookingNonumberREQUISNuméro de réservation

Retourne le booking avec les financials calculés.

▶ GET /bookings/:id/financials Recalculer les financials

Recalcule les données financières à partir de la source du booking.

Réponse

{
  "success": true,
  "financials": {
    "totalAmount": 1500, "cleaningFees": 100,
    "guestInsurance": 28.99, "taxes": 71.43,
    "platformFees": 45, "subtotal": 1328.57,
    "rentalIncome": 1194.58, "bankFees": 60,
    "managementFees": 298.65, "netIncome": 895.94
  }
}
▶ GET /bookings/:id Détail complet

Détail complet d'un booking avec financials calculés et résumé des paiements (confirmedPaid, pendingPaid).

ParamTypeDescription
idObjectIdREQUISID MongoDB du booking
Sécurité : Les champs passport, signature et authId ne sont jamais retournés.
▶ POST /bookings Créer un booking

BODY (JSON)

ChampTypeDescription
booking.bookingNonumberREQUISNuméro unique (par lot)
booking.lotIdObjectIdREQUISID du lot
booking.arrivalISO dateREQUISDate d'arrivée
booking.departureISO dateREQUISDate de départ
booking.totalAmountnumberREQUISMontant total TTC
booking.prepaidAmountnumberREQUISMontant pré-payé
booking.bedroomsnumberREQUISNombre de chambres
booking.tenantInformations.namestringREQUISNom du locataire
booking.tenantInformations.emailstringREQUISEmail du locataire
booking.statusstringDéfaut: "Booked"
booking.bookingSourcestringSource de la réservation
booking.cleaningFeesnumberFrais de ménage
booking.paymentMethodstringMéthode de paiement

Exemple

{
  "booking": {
    "bookingNo": 9999999,
    "lotId": "664f1a2b3c4d5e6f7a8b9c0d",
    "arrival": "2025-06-01",
    "departure": "2025-06-07",
    "totalAmount": 1500,
    "prepaidAmount": 1500,
    "bedrooms": 2,
    "bookingSource": "Manual",
    "tenantInformations": {
      "name": "John Doe",
      "email": "john@example.com"
    }
  }
}
409 Conflict : Si un booking avec ce numéro existe déjà pour ce lot.
▶ PATCH /bookings/:id Modifier un booking

BODY (JSON)

ChampTypeDescription
bookingData.statusstringNouveau statut
bookingData.arrivalISO dateNouvelle date d'arrivée
bookingData.departureISO dateNouvelle date de départ
bookingData.totalAmountnumberNouveau montant
bookingData.lotIdObjectId ou {_id}Nouveau lot
bookingData.tenantInformationsobjectname, email, phone, address...
bookingData.isExtensionbooleanMarquer/annuler une extension (pas de frais de nettoyage / assurance)
bookingData.*anycleaningFees, guestInsurance, services, noShow fields...
Sécurité : Les champs passport, signature, authId dans tenantInformations sont ignorés.
▶ PATCH /bookings/:id/check-in Effectuer le check-in

Passe le booking en status "Checked", calcule les financials et met à jour les infos tenant.

Pré-requis : Le booking doit être en status "Booked".

BODY (JSON)

ChampTypeDescription
tenantInformationsobject{ name, email, phone, address, companyName }
bookingSourcestringSource pour le calcul financier
cleaningFeesnumberFrais de ménage
totalAmountnumberMontant total
paymentMethodstringMéthode de paiement
guestInsurancenumberOptionnel (défaut: config source)
servicesarray[{ serviceId, title, price, quantity, total }]
servicesTotalnumberTotal services add-on
isSupervisorPresentbooleanSuperviseur présent au check-in
checkInEmployeestringNom de l'employé

Réponse

{
  "success": true,
  "message": "Check-in successful",
  "booking": { ... },
  "calculatedFinancials": {
    "totalAmount": 1500, "taxes": 71.43,
    "platformFees": 45, "bankFees": 60,
    "rentalIncome": 1194.58, ...
  },
  "note": "PDF generation and email sending are handled by the main API only."
}
Limitation : La Public API ne gère pas le chiffrement passeport/signature, la génération de PDFs, l'envoi d'emails ni les push notifications. Ces actions sont réservées à l'API principale.
▶ PATCH /bookings/:id/no-show Marquer no-show
ChampTypeDescription
isNoShowbooleantrue pour marquer, false pour annuler
noShowEmployeestringREQUISNom de l'employé
noShowNotestringNote optionnelle
noShowValidatedbooleanValidé par un superviseur
noShowConfirmedStepsbooleanÉtapes confirmées
▶ PATCH /bookings/:id/extension Marquer/annuler une extension
ChampTypeDescription
isExtensionbooleantrue pour marquer, false pour annuler (pas de frais de nettoyage / assurance)
▶ PATCH /bookings/:id/validate-payments Valider → status "Paid"
Pré-requis : Le booking doit être en status "Checked".

Change le status en "Paid".

Limitation : La création de transactions comptables, la génération de PDFs et l'envoi d'emails sont gérés par l'API principale uniquement.
▶ DELETE /bookings/:id Supprimer (soft delete)

Marque isDeleted=true. Le booking n'apparaît plus dans les listes.

▶ PUT /bookings/:id/restore Restaurer un booking supprimé

Remet isDeleted=false.

💳 Endpoints Paiements

▶ GET /booking-payments/booking/:bookingId Paiements d'un booking

Retourne tous les paiements non supprimés pour un booking donné.

▶ GET /booking-payments/booking/:bookingId/summary Résumé paiements

Réponse

{
  "success": true,
  "data": {
    "booking": { "_id": "...", "bookingNo": 1234, "totalAmount": 1500, "prepaidAmount": 1500 },
    "payments": {
      "count": 2, "totalPaid": 1200,
      "confirmedPaid": 800, "pendingPaid": 400,
      "remaining": 300, "isFullyPaid": false
    }
  }
}
▶ GET /booking-payments/all Tous les paiements (comptabilité)
ParamTypeDescription
page, limitnumberPagination
searchstringRecherche par numéro ou nom tenant
lotIdObjectIdFiltrer par lot
bookingSourcestringSource
paymentMethodstringMéthode de paiement
paymentStatusstring"pending" ou "confirmed"
paymentDateStartISO dateDate début
paymentDateEndISO dateDate fin
amountMinnumberMontant minimum
amountMaxnumberMontant maximum
▶ GET /booking-payments/bookings Bookings avec paiements groupés
ParamTypeDescription
searchstringNuméro ou nom
lotId, status, bookingSourcestringFiltres booking
startDate, endDateISO datePériode
hasPaymentsbooleanAvec paiements
hasPendingPaymentsbooleanAvec paiements en attente
paymentStatusstring"pending" ou "confirmed"
paymentDateStart, paymentDateEndISO datePériode paiement
▶ POST /booking-payments Créer un paiement
ChampTypeDescription
bookingIdObjectIdREQUISID du booking
amountnumberREQUISMontant
paymentMethodstringAirbnb, Expedia, Payo, VRBO, Credit Card, Cash, Bank Transfer, Check, Other
paymentDateISO dateDéfaut: maintenant
descriptionstringDescription
referencestringRéférence
▶ PUT /booking-payments/:paymentId Modifier un paiement

Champs modifiables : amount, paymentMethod, paymentDate, description, reference.

▶ PATCH /booking-payments/:paymentId/confirm Confirmer un paiement

Marque le paiement comme confirmé avec la date de confirmation.

▶ PATCH /booking-payments/bulk-update-status Confirmer/infirmer en masse
ChampTypeDescription
paymentIdsstring[]REQUISListe des IDs de paiements
isConfirmedbooleantrue = confirmer, false = infirmer
▶ DELETE /booking-payments/:paymentId Supprimer un paiement

Soft delete (isDeleted=true).

💻 Exemples de code

JavaScript
Python
cURL
const API = 'https://listings.4u-realestate.org';
const KEY = 'YOUR_API_KEY';
const headers = { 'x-api-key': KEY, 'Content-Type': 'application/json' };

// Lister les bookings
const res = await fetch(`${API}/bookings?status=Booked&limit=10`, { headers });
const data = await res.json();
console.log(`Total: ${data.total} bookings`);

// Créer un booking
const created = await fetch(`${API}/bookings`, {
  method: 'POST', headers,
  body: JSON.stringify({
    booking: {
      bookingNo: 9999999, lotId: 'LOT_ID',
      arrival: '2025-06-01', departure: '2025-06-07',
      totalAmount: 1500, prepaidAmount: 1500, bedrooms: 2,
      bookingSource: 'Manual',
      tenantInformations: { name: 'John Doe', email: 'john@ex.com' }
    }
  })
});

// Check-in
await fetch(`${API}/bookings/${bookingId}/check-in`, {
  method: 'PATCH', headers,
  body: JSON.stringify({
    tenantInformations: { name: 'John Doe', email: 'john@ex.com', phone: '+1234' },
    bookingSource: 'Manual', cleaningFees: 100,
    totalAmount: 1500, paymentMethod: 'Credit Card',
    services: [], servicesTotal: 0, checkInEmployee: 'Agent'
  })
});

// Ajouter un paiement
await fetch(`${API}/booking-payments`, {
  method: 'POST', headers,
  body: JSON.stringify({
    bookingId: 'BOOKING_ID', amount: 750,
    paymentMethod: 'Credit Card', description: 'First payment'
  })
});
import requests

API = 'https://listings.4u-realestate.org'
HEADERS = {'x-api-key': 'YOUR_KEY', 'Content-Type': 'application/json'}

# Lister
r = requests.get(f'{API}/bookings', params={'status': 'Booked', 'limit': 10}, headers=HEADERS)
print(f"Total: {r.json()['total']} bookings")

# Créer
r = requests.post(f'{API}/bookings', json={
    'booking': {
        'bookingNo': 9999999, 'lotId': 'LOT_ID',
        'arrival': '2025-06-01', 'departure': '2025-06-07',
        'totalAmount': 1500, 'prepaidAmount': 1500, 'bedrooms': 2,
        'tenantInformations': {'name': 'John Doe', 'email': 'john@ex.com'}
    }
}, headers=HEADERS)

# Check-in
r = requests.patch(f'{API}/bookings/{booking_id}/check-in', json={
    'tenantInformations': {'name': 'John Doe', 'email': 'john@ex.com'},
    'bookingSource': 'Manual', 'cleaningFees': 100,
    'totalAmount': 1500, 'paymentMethod': 'Credit Card',
    'services': [], 'servicesTotal': 0
}, headers=HEADERS)

# Paiement
r = requests.post(f'{API}/booking-payments', json={
    'bookingId': 'ID', 'amount': 750, 'paymentMethod': 'Credit Card'
}, headers=HEADERS)
# Lister les bookings
curl -H "x-api-key: KEY" "https://listings.4u-realestate.org/bookings?status=Booked&limit=10"

# Détail
curl -H "x-api-key: KEY" "https://listings.4u-realestate.org/bookings/BOOKING_ID"

# Vue comptable
curl -H "x-api-key: KEY" "https://listings.4u-realestate.org/bookings/accounting?status=Paid"

# Créer
curl -X POST -H "x-api-key: KEY" -H "Content-Type: application/json" \
  -d '{"booking":{"bookingNo":9999999,"lotId":"LOT_ID","arrival":"2025-06-01","departure":"2025-06-07","totalAmount":1500,"prepaidAmount":1500,"bedrooms":2,"tenantInformations":{"name":"John Doe","email":"john@ex.com"}}}' \
  "https://listings.4u-realestate.org/bookings"

# Check-in
curl -X PATCH -H "x-api-key: KEY" -H "Content-Type: application/json" \
  -d '{"tenantInformations":{"name":"John","email":"john@ex.com"},"bookingSource":"Manual","cleaningFees":100,"totalAmount":1500,"paymentMethod":"Credit Card","services":[],"servicesTotal":0}' \
  "https://listings.4u-realestate.org/bookings/BOOKING_ID/check-in"

# Paiement
curl -X POST -H "x-api-key: KEY" -H "Content-Type: application/json" \
  -d '{"bookingId":"BOOKING_ID","amount":750,"paymentMethod":"Credit Card"}' \
  "https://listings.4u-realestate.org/booking-payments"

# Confirmer paiement
curl -X PATCH -H "x-api-key: KEY" \
  "https://listings.4u-realestate.org/booking-payments/PAYMENT_ID/confirm"

# Valider → Paid
curl -X PATCH -H "x-api-key: KEY" \
  "https://listings.4u-realestate.org/bookings/BOOKING_ID/validate-payments"

⚠️ Codes d'erreur

CodeSignificationSolution
400Données invalides ou manquantesVérifier les champs requis
401Clé API manquante ou invalideAjouter un header x-api-key valide
403Clé API désactivéeContacter l'admin
404Booking/paiement non trouvéVérifier l'ID
409Doublon (bookingNo + lotId)Utiliser un numéro unique
429Rate limit dépasséAttendre et réessayer
500Erreur serveurContacter contact@4u-realestate.org