{"openapi":"3.1.0","info":{"title":"Node — API publique","version":"1.0.0","description":"API publique de Node Channel Manager.\n\n## Ce qu'il faut savoir avant de commencer\n\n**Cette API n'est pas `/api/*`.** Les routes internes de l'application changent à chaque déploiement et n'ont aucun contrat. `/api/v1` est figée : les champs décrits ici ne disparaissent pas sans un préavis de six mois, annoncé par un en-tête `Node-Deprecation`.\n\n**Les écritures de distribution sont asynchrones.** Ouvrir des dates ou changer un prix répond **202** avec un `job_id`, jamais 200. Node ne possède pas sa connectivité OTA : elle passe par un gestionnaire de canaux dont le quota est partagé par tous les clients. Une écriture synchrone dégraderait la synchronisation de tout le monde. Suivez votre travail sur `GET /v1/jobs/{id}`.\n\n**Écrivez par lot.** Une boucle date par date est le meilleur moyen d'épuiser votre quota et de faire rejeter vos envois. Groupez vos plages : jusqu'à 60 par appel.\n\n**N'interrogez pas en boucle.** Pour être prévenu d'une nouvelle réservation, déclarez un webhook (`POST /v1/webhooks`). Le polling est plafonné, et il ne verra jamais un événement plus tôt qu'un webhook.\n\n## Authentification\n\nEn-tête `Authorization: Bearer node_sk_…` (jeton personnel, créé dans Réglages → API & MCP) ou `node_at_…` (jeton OAuth d'une application partenaire).\n\nUn jeton porte des **portées** : il ne peut faire que ce qui a été explicitement coché. Vérifiez les vôtres avec `GET /v1/me` avant de construire votre intégration — c'est plus rapide que de découvrir un 403 en pleine boucle.","contact":{"name":"Node","url":"https://nodegroupe.com"}},"servers":[{"url":"https://app.nodegroupe.com","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Jeton `node_sk_…` (personnel) ou `node_at_…` (OAuth)."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}},"headers":{"RateLimit-Limit":{"schema":{"type":"integer"},"description":"Plafond du seau pour la minute en cours."},"RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Appels restants."},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Epoch (s) de remise à zéro."},"Node-Request-Id":{"schema":{"type":"string"},"description":"Identifiant de la requête — à citer dans toute demande de support."}}},"x-scopes":{"properties:read":{"label":"Logements — lecture","description":"Fiches logement, équipements, adresses, photos.","sensitive":false,"warning":null},"properties:write":{"label":"Logements — écriture","description":"Créer et modifier des logements.","sensitive":false,"warning":"Une création déclenche le géocodage puis la création côté Channex."},"owners:read":{"label":"Propriétaires — lecture","description":"Propriétaires et rattachements aux logements.","sensitive":false,"warning":"Contient des données personnelles : chaque accès est journalisé."},"owners:write":{"label":"Propriétaires — écriture","description":"Créer et modifier des propriétaires, gérer les rattachements.","sensitive":false,"warning":null},"reservations:read":{"label":"Réservations — lecture","description":"Séjours, statuts, canaux, montants.","sensitive":false,"warning":null},"reservations:write":{"label":"Réservations — écriture","description":"Créer une réservation directe, modifier ou bloquer des dates.","sensitive":false,"warning":"Ne permet AUCUNE suppression : une suppression de séjour lève les dates chez Channex sans trace."},"availability:read":{"label":"Disponibilités — lecture","description":"Calendrier des dates ouvertes et fermées.","sensitive":false,"warning":null},"availability:write":{"label":"Disponibilités — écriture","description":"Ouvrir et fermer des dates à la vente.","sensitive":false,"warning":"Écriture asynchrone : la réponse est un 202 avec un identifiant de travail."},"rates:read":{"label":"Tarifs — lecture","description":"Prix, restrictions et durées de séjour.","sensitive":false,"warning":null},"rates:write":{"label":"Tarifs — écriture","description":"Modifier prix, restrictions et durées minimales de séjour.","sensitive":true,"warning":"La portée la plus lourde de conséquences : elle écrit sur le quota Channex, PARTAGÉ par toute la flotte. Écriture par lot et asynchrone uniquement."},"messages:read":{"label":"Messagerie — lecture","description":"Conversations voyageur et historique des messages.","sensitive":true,"warning":"Le contenu d'un échange avec un voyageur est une donnée personnelle : sa transmission à un tiers relève de la sous-traitance."},"messages:send":{"label":"Messagerie — envoi","description":"Envoyer un message à un voyageur.","sensitive":true,"warning":"Les messages transitent par Airbnb et Booking, qui appliquent leurs propres règles anti-spam : un envoi excessif fait suspendre VOTRE compte OTA."},"cleaning:read":{"label":"Ménage — lecture","description":"Missions, prestataires, états des lieux, incidents.","sensitive":false,"warning":null},"cleaning:write":{"label":"Ménage — écriture","description":"Créer, attribuer et clôturer des missions de ménage.","sensitive":false,"warning":null},"billing:read":{"label":"Facturation — lecture","description":"Factures propriétaire, décomptes et honoraires.","sensitive":false,"warning":"Lecture seule : l'API n'écrit jamais sur la facturation ni sur Stripe."},"guests.pii:read":{"label":"Identité des voyageurs","description":"Fiches de police et pièces d'identité.","sensitive":true,"warning":"Données d'identité. Chaque accès est journalisé nominativement ; un partenaire doit avoir signé un accord de sous-traitance."},"locks.codes:read":{"label":"Codes d'accès","description":"Codes de serrure et de boîte à clés, en clair.","sensitive":true,"warning":"Un code qui fuite, c'est un accès physique au logement."},"webhooks:manage":{"label":"Webhooks","description":"Déclarer et retirer ses points de réception d'événements.","sensitive":false,"warning":null}},"x-rate-limits":{"standard":{"read":600,"write":60,"ari":2},"partner":{"read":3000,"write":300,"ari":5},"internal":{"read":60000,"write":6000,"ari":60}},"x-webhook-events":{"reservation.created":{"description":"Une réservation est entrée (tout canal confondu).","required_scope":"reservations:read"},"reservation.updated":{"description":"Dates, montants ou occupation d'une réservation ont changé.","required_scope":"reservations:read"},"reservation.cancelled":{"description":"Une réservation a été annulée.","required_scope":"reservations:read"},"stay.checked_in":{"description":"Le voyageur est arrivé.","required_scope":"reservations:read"},"stay.checked_out":{"description":"Le voyageur est parti.","required_scope":"reservations:read"},"message.received":{"description":"Un voyageur a écrit.","required_scope":"messages:read"},"message.sent":{"description":"Un message est parti vers le voyageur (conciergerie, équipe, ou écrit depuis l'extranet de l'OTA).","required_scope":"messages:read"},"message.updated":{"description":"Un message a été modifié ou supprimé chez l'OTA.","required_scope":"messages:read"},"cleaning.mission.assigned":{"description":"Une mission de ménage a été attribuée.","required_scope":"cleaning:read"},"cleaning.mission.completed":{"description":"Une mission de ménage est terminée.","required_scope":"cleaning:read"},"invoice.issued":{"description":"Une facture propriétaire a été émise.","required_scope":"billing:read"},"invoice.paid":{"description":"Une facture propriétaire a été payée.","required_scope":"billing:read"},"property.created":{"description":"Un logement a été créé.","required_scope":"properties:read"},"property.updated":{"description":"Un logement a été modifié.","required_scope":"properties:read"},"incident.reported":{"description":"Un incident a été signalé.","required_scope":"cleaning:read"}},"paths":{"/api/v1/me":{"get":{"summary":"Identité du jeton","description":"Le premier appel à faire : il dit si le jeton fonctionne et ce qu'il a le droit de faire, sans toucher la moindre donnée métier. Aucune portée requise.","responses":{"200":{"description":"Compte, portées et quotas"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}}},"/api/v1/properties":{"get":{"summary":"Lister les logements","x-scope":"properties:read","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Valeur `next_cursor` de la réponse précédente. Jamais un numéro de page : un offset dérive dès qu'une ligne s'insère pendant la boucle."},{"name":"search","in":"query","schema":{"type":"string"},"description":"Nom, ville ou nom interne."},{"name":"city","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Page de logements"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}},"post":{"summary":"Créer un logement","x-scope":"properties:write","description":"Déclenche le géocodage puis la création côté gestionnaire de canaux. L'opération peut aboutir avec un avertissement de synchronisation si des informations manquent (adresse incomplète, téléphone absent) — le logement est alors créé dans Node mais pas encore distribuable.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"Clé unique par tentative logique. Rejouer la même clé sous 24 h renvoie la première réponse au lieu de créer un doublon."}],"responses":{"201":{"description":"Logement créé"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"422":{"description":"Informations insuffisantes","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}}},"/api/v1/properties/{id}":{"get":{"summary":"Détail d'un logement","x-scope":"properties:read","responses":{"200":{"description":"Logement"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}},"patch":{"summary":"Modifier un logement","x-scope":"properties:write","description":"Le prix de base et la devise ne sont PAS modifiables ici : ils toucheraient la distribution. Passez par `PUT /v1/rates`.","responses":{"200":{"description":"Logement modifié"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}}},"/api/v1/owners":{"get":{"summary":"Lister les propriétaires","x-scope":"owners:read","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Valeur `next_cursor` de la réponse précédente. Jamais un numéro de page : un offset dérive dès qu'une ligne s'insère pendant la boucle."}],"responses":{"200":{"description":"Page de propriétaires"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}},"post":{"summary":"Créer un propriétaire","x-scope":"owners:write","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"Clé unique par tentative logique. Rejouer la même clé sous 24 h renvoie la première réponse au lieu de créer un doublon."}],"responses":{"201":{"description":"Propriétaire créé"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}}},"/api/v1/reservations":{"get":{"summary":"Lister les réservations","x-scope":"reservations:read","description":"`updated_since` sert au RATTRAPAGE après une panne de votre côté. Ce n'est pas un substitut aux webhooks : interroger cette route en boucle pour détecter les nouvelles réservations est précisément la charge que les webhooks évitent.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Valeur `next_cursor` de la réponse précédente. Jamais un numéro de page : un offset dérive dès qu'une ligne s'insère pendant la boucle."},{"name":"property_id","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","schema":{"type":"string"}},{"name":"channel","in":"query","schema":{"type":"string"}},{"name":"arrival_from","in":"query","schema":{"type":"string","format":"date"}},{"name":"arrival_to","in":"query","schema":{"type":"string","format":"date"}},{"name":"updated_since","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Page de réservations"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}},"post":{"summary":"Créer une réservation directe ou un blocage","x-scope":"reservations:write","description":"Aucune SUPPRESSION n'existe en v1 : supprimer un séjour lève les dates chez le gestionnaire de canaux sans laisser de trace. Pour libérer des dates, passez le statut à `cancelled`, qui est tracé et réversible.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"Clé unique par tentative logique. Rejouer la même clé sous 24 h renvoie la première réponse au lieu de créer un doublon."}],"responses":{"201":{"description":"Réservation créée"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}}},"/api/v1/availability":{"get":{"summary":"Calendrier d'un logement","x-scope":"availability:read","description":"Lu depuis le cache local, pas en direct chez l'OTA. `cached_at` donne l'âge de la donnée.","parameters":[{"name":"property_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"from","in":"query","required":true,"schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":true,"schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Jours du calendrier"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}},"put":{"summary":"Ouvrir ou fermer des dates","x-scope":"availability:write","description":"Réponse **202**. L'écriture est déposée dans une file et poussée par lots. Une plage qui commence dans le passé est refusée ENTIÈREMENT, partie future comprise : c'est le comportement du gestionnaire de canaux, et le masquer produirait un calendrier qu'on croit à jour alors qu'il ne l'est pas.","responses":{"202":{"description":"Accepté — suivez `job_id`"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"422":{"description":"Logement non connecté aux canaux","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}}},"/api/v1/rates":{"get":{"summary":"Prix et restrictions","x-scope":"rates:read","description":"`price_on_channel` est ce que le canal AFFICHE (sa majoration comprise) ; `price_pushed` est ce que Node a ENVOYÉ. Les deux ne coïncident jamais sur un canal qui majore — les comparer directement donne 0 % de correspondance.","parameters":[{"name":"property_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"from","in":"query","required":true,"schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":true,"schema":{"type":"string","format":"date"}},{"name":"rate_plan_id","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Jours tarifés"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}},"put":{"summary":"Écrire prix et restrictions","x-scope":"rates:write","description":"Réponse **202**. Portée la plus lourde de conséquences du catalogue : elle écrit sur un quota partagé par tous les clients de Node. Seau dédié, très étroit (2 appels/minute en palier standard). Groupez vos plages — jusqu'à 60 par appel.","responses":{"202":{"description":"Accepté — suivez `job_id`"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"422":{"description":"Logement non connecté, ou plan tarifaire inconnu","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}}},"/api/v1/jobs/{id}":{"get":{"summary":"Suivi d'une écriture asynchrone","description":"Le pendant obligatoire du 202. `status` vaut `pending`, `processing`, `completed` ou `failed`. Un seul échec parmi les opérations suffit à marquer le travail `failed` : annoncer « terminé » alors qu'une partie des dates n'est pas partie serait le pire des mensonges pour un calendrier.","responses":{"200":{"description":"État du travail"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}}},"/api/v1/cleaning/missions":{"get":{"summary":"Lister les missions de ménage","x-scope":"cleaning:read","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Valeur `next_cursor` de la réponse précédente. Jamais un numéro de page : un offset dérive dès qu'une ligne s'insère pendant la boucle."}],"responses":{"200":{"description":"Page de missions"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}},"post":{"summary":"Créer une mission","x-scope":"cleaning:write","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"Clé unique par tentative logique. Rejouer la même clé sous 24 h renvoie la première réponse au lieu de créer un doublon."}],"responses":{"201":{"description":"Mission créée"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}}},"/api/v1/invoices":{"get":{"summary":"Factures propriétaire","x-scope":"billing:read","description":"LECTURE SEULE, définitivement. Commissions, TVA et taxe de séjour s'y croisent : une erreur d'intégration deviendrait une erreur comptable chez un tiers.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Valeur `next_cursor` de la réponse précédente. Jamais un numéro de page : un offset dérive dès qu'une ligne s'insère pendant la boucle."}],"responses":{"200":{"description":"Page de factures"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}}},"/api/v1/messages":{"get":{"summary":"Conversations et messages","x-scope":"messages:read","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Valeur `next_cursor` de la réponse précédente. Jamais un numéro de page : un offset dérive dès qu'une ligne s'insère pendant la boucle."}],"responses":{"200":{"description":"Conversations, ou messages si `conversation_id` est fourni"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}},"post":{"summary":"Envoyer un message au voyageur","x-scope":"messages:send","description":"Le message transite par Airbnb ou Booking, qui appliquent leurs propres règles anti-spam : un envoi excessif fait suspendre VOTRE compte OTA. `Idempotency-Key` est obligatoire — un rejeu enverrait un doublon au voyageur, qui le lit.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"Clé unique par tentative logique. Rejouer la même clé sous 24 h renvoie la première réponse au lieu de créer un doublon."}],"responses":{"201":{"description":"Message envoyé"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"502":{"description":"Le canal a refusé le message","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}}},"/api/v1/webhooks":{"get":{"summary":"Points de réception et catalogue d'événements","x-scope":"webhooks:manage","responses":{"200":{"description":"Points de réception + événements disponibles"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}},"post":{"summary":"Déclarer un point de réception","x-scope":"webhooks:manage","description":"L'URL doit être en HTTPS et publique. Le secret de signature est renvoyé UNE SEULE FOIS. S'abonner à un événement exige en plus la portée de lecture correspondante — sinon `webhooks:manage` serait une porte dérobée vers des données qu'on n'a pas le droit de lire.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":200},"description":"Clé unique par tentative logique. Rejouer la même clé sous 24 h renvoie la première réponse au lieu de créer un doublon."}],"responses":{"201":{"description":"Créé — `secret` visible une seule fois"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}}},"/api/v1/webhooks/{id}":{"get":{"summary":"État et 20 dernières livraisons","x-scope":"webhooks:manage","responses":{"200":{"description":"Point de réception + historique"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}},"delete":{"summary":"Retirer un point de réception","x-scope":"webhooks:manage","responses":{"200":{"description":"Retiré"},"400":{"description":"Requête invalide","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"401":{"description":"Jeton absent, invalide, expiré ou révoqué","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"403":{"description":"Portée manquante (`scope_missing`)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"404":{"description":"Introuvable — ou hors du périmètre du jeton","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}},"429":{"description":"Quota dépassé. Voir `Retry-After`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"scope_missing"},"message":{"type":"string"},"field":{"type":"string","nullable":true},"request_id":{"type":"string","format":"uuid"}},"required":["code","message","request_id"]}}}}}}}}}}}