{
  "openapi": "3.1.0",
  "info": {
    "title": "Passerelle Amenitiz",
    "version": "1.0.0",
    "description": "API publique, protégée par clé, qui expose l’API interne du PMS Amenitiz d’une propriété.\n\n**Ce n’est pas une API officielle Amenitiz.** Elle rejoue les appels que fait l’interface d’administration, depuis un navigateur authentifié par le cookie de session du compte. Amenitiz peut la casser sans préavis ; toute route peut donc disparaître au prochain déploiement de leur côté.\n\n**Lecture seule.** Aucune route n’écrit chez Amenitiz.\n\n**Données personnelles.** Les réponses contiennent des noms de clients et des états de paiement. Traiter la clé comme un secret, ne pas mettre les réponses en cache côté public."
  },
  "servers": [
    {
      "url": "https://amenitiz.endurance.tools"
    }
  ],
  "security": [
    {
      "CleApi": []
    },
    {
      "Porteur": []
    }
  ],
  "tags": [
    {
      "name": "Référentiel"
    },
    {
      "name": "Planning"
    },
    {
      "name": "Opérations"
    },
    {
      "name": "Performance"
    },
    {
      "name": "Client et dossier"
    },
    {
      "name": "Facturation"
    },
    {
      "name": "Contexte tarifaire"
    },
    {
      "name": "Messagerie"
    },
    {
      "name": "Grille"
    },
    {
      "name": "Passe-plat"
    },
    {
      "name": "Service"
    }
  ],
  "components": {
    "securitySchemes": {
      "CleApi": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      },
      "Porteur": {
        "type": "http",
        "scheme": "bearer",
        "description": "En-tête `Authorization: Bearer <clé>`."
      }
    },
    "schemas": {
      "Meta": {
        "type": "object",
        "properties": {
          "route": {
            "type": "string"
          },
          "cache": {
            "type": "string",
            "enum": [
              "succès",
              "partiel",
              "absent",
              "périmé"
            ]
          },
          "age_ms": {
            "type": "integer"
          },
          "fetched_at": {
            "type": "string",
            "format": "date-time"
          },
          "upstream": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "upstream_budget_remaining": {
            "type": "integer"
          },
          "warning": {
            "type": "string"
          }
        }
      },
      "Erreur": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "responses": {
      "Erreur": {
        "description": "Erreur",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erreur"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/v1/rooms": {
      "get": {
        "operationId": "rooms",
        "summary": "Les chambres physiques, avec leur numéro, leur nom et leur état de ménage.",
        "tags": [
          "Référentiel"
        ],
        "description": "Source amont : GET /fr/api/v2/property/rooms.",
        "parameters": [
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "Tri par numéro de chambre."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/room-categories": {
      "get": {
        "operationId": "room_categories",
        "summary": "Les catégories de chambre et leur tarif par défaut.",
        "tags": [
          "Référentiel"
        ],
        "description": "Source amont : GET /fr/api/v2/property/room_categories.",
        "parameters": [
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/reservations": {
      "get": {
        "operationId": "reservations",
        "summary": "Les réservations chevauchant la fenêtre demandée, chambre par chambre.",
        "tags": [
          "Planning"
        ],
        "description": "Une réservation occupe la nuit du jour J si arrival_date ≤ J < departure_date. La fenêtre remonte donc aussi les départs du premier jour et les arrivées du dernier : filtrer côté appelant. Champs à ne pas croire : `has_comments` vaut exactement « source = Booking.com » (ce n’est pas une note client), `check_in_status` vaut `without_status` partout (la fonction check-in n’est pas utilisée), `extras` est toujours vide. Vérifié sur 422 réservations le 01/09/2026. `payment_status`, lui, est fiable.\n\nSource amont : GET /fr/api/v2/reservations/rooms.",
        "parameters": [
          {
            "name": "start_date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Premier jour de la fenêtre (inclus)."
          },
          {
            "name": "end_date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Dernier jour de la fenêtre (exclu pour les nuitées)."
          },
          {
            "name": "booking_statuses",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Statuts retenus. Défaut = les quatre statuts « vivants ». Ajouter `cancelled` fait remonter les annulations. Valeurs séparées par des virgules. Admises : confirmed, modified, quote_confirmed, quote_pending, cancelled, no_show. Valeur par défaut calculée à l’appel."
          },
          {
            "name": "unallocated",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = uniquement les réservations SANS chambre attribuée (anomalie à surveiller)."
          },
          {
            "name": "room_category_ids",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Identifiants de catégorie, séparés par des virgules. Omis = toutes. Volontairement optionnel : une catégorie créée plus tard serait absente d’une liste figée. Valeurs séparées par des virgules."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/blocks": {
      "get": {
        "operationId": "blocks",
        "summary": "Les blocages manuels du calendrier : fermetures, travaux, et séjours saisis à la main.",
        "tags": [
          "Planning"
        ],
        "description": "🔑 À ne jamais ignorer. Le 01/09/2026, quatre chambres sur neuf étaient occupées par des séjours saisis en BLOCAGE et non en réservation : ne lire que /v1/reservations sous-estimait l’occupation de moitié. Un blocage ne porte AUCUN nombre de personnes, et son statut ne dit pas s’il s’agit d’un client ou d’une fermeture — un `ooi` « hors vente » portait un nom de personne, un `ooo` portait « Travaux ». Le motif est du texte libre.\n\nSource amont : GET /fr/api/v2/calendar/rooms.",
        "parameters": [
          {
            "name": "start_date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Premier jour de la fenêtre (inclus)."
          },
          {
            "name": "end_date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Dernier jour de la fenêtre (exclu pour les nuitées)."
          },
          {
            "name": "statuses",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Types de blocage retenus. Valeurs séparées par des virgules. Admises : ooi, ooo, pre_booked. Valeur par défaut calculée à l’appel."
          },
          {
            "name": "room_category_ids",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Identifiants de catégorie, séparés par des virgules. Omis = toutes. Volontairement optionnel : une catégorie créée plus tard serait absente d’une liste figée. Valeurs séparées par des virgules."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/unavailable-rooms": {
      "get": {
        "operationId": "unavailable_rooms",
        "summary": "Les couples chambre/date fermés à la vente.",
        "tags": [
          "Planning"
        ],
        "description": "Source amont : GET /fr/api/v2/calendar/unavailable_rooms.",
        "parameters": [
          {
            "name": "start_date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Premier jour de la fenêtre (inclus)."
          },
          {
            "name": "end_date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Dernier jour de la fenêtre (exclu pour les nuitées)."
          },
          {
            "name": "room_category_ids",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Identifiants de catégorie, séparés par des virgules. Omis = toutes. Volontairement optionnel : une catégorie créée plus tard serait absente d’une liste figée. Valeurs séparées par des virgules."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/pricing": {
      "get": {
        "operationId": "pricing",
        "summary": "Les prix posés par catégorie et par date, pour un plan tarifaire.",
        "tags": [
          "Planning"
        ],
        "description": "Seules les dates dont le prix a été explicitement modifié apparaissent ; ailleurs, c’est le `default_price` de la catégorie qui s’applique.\n\nSource amont : GET /fr/api/v2/pricing/rate_plans/{plan}/pricing_calendars.",
        "parameters": [
          {
            "name": "start_date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Premier jour de la fenêtre (inclus)."
          },
          {
            "name": "end_date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Dernier jour de la fenêtre (exclu pour les nuitées)."
          },
          {
            "name": "rate_plan",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "default"
            },
            "description": "Plan tarifaire. Seul « default » (Standard) est confirmé sur cette propriété."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/day": {
      "get": {
        "operationId": "day",
        "summary": "Vue consolidée d’une nuit : qui occupe quelle chambre, réservations ET blocages réunis.",
        "tags": [
          "Planning"
        ],
        "description": "La route à utiliser en premier. Elle fusionne les deux sources d’occupation, rattrape les longs séjours commencés avant la fenêtre, et distingue arrivées, départs et recouches. Un blocage n’ayant pas de nombre de personnes, `personnes` y vaut null — jamais 0.\n\nSource amont : GET /fr/api/v2/property/rooms, GET /fr/api/v2/property/room_categories, GET /fr/api/v2/reservations/rooms, GET /fr/api/v2/calendar/rooms.",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Jour observé (défaut : aujourd’hui, fuseau Europe/Paris). Valeur par défaut calculée à l’appel."
          },
          {
            "name": "history_days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 400,
              "default": 180
            },
            "description": "Profondeur de rattrapage des séjours commencés avant la date. 180 j couvre les blocages à l’année."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/dashboard": {
      "get": {
        "operationId": "dashboard",
        "summary": "Les indicateurs du jour : occupation, chiffre d’affaires, ADR, RevPAR, pickup.",
        "tags": [
          "Opérations"
        ],
        "description": "Source amont : GET /fr/api/v1/dashboards/hero, GET /fr/api/v1/dashboards/todays_work_counts.",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Jour observé (défaut : aujourd’hui, fuseau Europe/Paris). Valeur par défaut calculée à l’appel."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/arrivals": {
      "get": {
        "operationId": "arrivals",
        "summary": "Les arrivées du jour, prêtes à afficher : chambre, effectif, paiement, état de propreté.",
        "tags": [
          "Opérations"
        ],
        "description": "Source amont : GET /fr/api/v1/dashboards/arrivals.",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Jour observé (défaut : aujourd’hui, fuseau Europe/Paris). Valeur par défaut calculée à l’appel."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/housekeeping": {
      "get": {
        "operationId": "housekeeping",
        "summary": "L’état d’entretien chambre par chambre : recouche, départ, arrivée, effectif, affectation.",
        "tags": [
          "Opérations"
        ],
        "description": "Source amont : GET /fr/api/v1/housekeeping/individual_rooms.json.",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Jour observé (défaut : aujourd’hui, fuseau Europe/Paris). Valeur par défaut calculée à l’appel."
          },
          {
            "name": "sort_by",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "number",
                "status",
                "name"
              ],
              "default": "number"
            },
            "description": "Critère de tri."
          },
          {
            "name": "sort_direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "Sens du tri."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/housekeeping/board": {
      "get": {
        "operationId": "housekeeping_board",
        "summary": "Le tableau d’entretien tel que l’affiche le tableau de bord : recouches, remises à blanc, chambres prêtes.",
        "tags": [
          "Opérations"
        ],
        "description": "Source amont : GET /fr/api/v1/dashboards/operations/housekeeping.",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Jour observé (défaut : aujourd’hui, fuseau Europe/Paris). Valeur par défaut calculée à l’appel."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/housekeeping/staff": {
      "get": {
        "operationId": "housekeeping_staff",
        "summary": "Le personnel d’entretien déclaré.",
        "tags": [
          "Opérations"
        ],
        "description": "Source amont : GET /fr/api/v1/housekeeping/staff.",
        "parameters": [
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/payments/summary": {
      "get": {
        "operationId": "payments_summary",
        "summary": "Le bandeau AmenitizPay : prochain versement, encaissements du jour, paiements en échec.",
        "tags": [
          "Opérations"
        ],
        "description": "Source amont : GET /fr/api/v1/dashboards/apay_strip.",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Jour observé (défaut : aujourd’hui, fuseau Europe/Paris). Valeur par défaut calculée à l’appel."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/performance/{metric}": {
      "get": {
        "operationId": "performance",
        "summary": "Séries temporelles : occupation, ADR, RevPAR, chiffre d’affaires, délai de réservation, durée de séjour.",
        "tags": [
          "Performance"
        ],
        "description": "⚠️ Piège de l’amont : le paramètre s’appelle `room_ids` mais attend des identifiants de CATÉGORIE. Ici il s’appelle `room_category_ids`, ce qu’il est réellement. Omis, toutes les catégories sont prises. Le champ `value` porte selon la métrique un pourcentage, un montant, des jours ou des nuits (voir `unit`). ⚠️ `granularity` est SANS EFFET : vérifié le 01/09/2026, daily, weekly et monthly renvoient une série strictement identique (même empreinte). L’amont ne sert que du quotidien et c’est la SPA qui agrège. La réponse annonce donc toujours `granularity: \"daily\"`, qui est la vérité de la donnée.\n\nSource amont : GET /fr/api/v1/performance/{metric}_report.json.",
        "parameters": [
          {
            "name": "metric",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "occupancy",
                "adr",
                "revenue",
                "revpar",
                "lead_time",
                "length_of_stay"
              ]
            }
          },
          {
            "name": "start_date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Premier jour de la fenêtre (inclus)."
          },
          {
            "name": "end_date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Dernier jour de la fenêtre (exclu pour les nuitées)."
          },
          {
            "name": "room_category_ids",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Identifiants de catégorie, séparés par des virgules. Omis = toutes. Volontairement optionnel : une catégorie créée plus tard serait absente d’une liste figée. Valeurs séparées par des virgules."
          },
          {
            "name": "granularity",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "daily",
                "weekly",
                "monthly"
              ],
              "default": "daily"
            },
            "description": "⚠️ SANS EFFET, accepté par fidélité à l’amont. La série est toujours quotidienne — agréger côté appelant."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/bookings/{booking_id}": {
      "get": {
        "operationId": "booking",
        "summary": "Le dossier complet d’une réservation : coordonnées du client, montants au centime, détail par chambre.",
        "tags": [
          "Client et dossier"
        ],
        "description": "🔑 C’est le SEUL endroit où l’on obtient e-mail et téléphone du client — ni /v1/reservations ni /v1/day ne les portent, ils s’arrêtent au nom. L’identifiant à passer est le `booking.id` renvoyé par /v1/reservations, ou `booking_id` dans /v1/day. ⚠️ Sur un échantillon de 10 dossiers récents : e-mail et téléphone renseignés 10 fois sur 10, mais l’adresse des réservations OTA est un ALIAS de relais (@guest.booking.com) — utilisable pour joindre le client pendant le séjour, sans valeur pour une base de contacts durable. Les réservations directes portent le vrai domaine. Les montants sont en CENTIMES et incluent `city_tax_cents` (taxe de séjour), absente partout ailleurs.\n\nSource amont : GET /fr/api/v1/bookings/{id}.",
        "parameters": [
          {
            "name": "booking_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/clients": {
      "get": {
        "operationId": "clients",
        "summary": "Une liste de clients avec e-mail, téléphone, ville et pays.",
        "tags": [
          "Client et dossier"
        ],
        "description": "⚠️ Endpoint bridé, à ne pas prendre pour un annuaire. Il renvoie toujours les MÊMES 50 fiches, dans un ordre qui n’est ni l’ordre d’identifiant ni une date : c’est la liste qui alimente un sélecteur dans l’interface, pas un export. Sept noms de paramètre de pagination et de recherche ont été essayés le 01/09/2026 (`page`, `per_page`, `search`, `q`, `term`, `name`, `query`) : tous ignorés, réponse identique. Pour retrouver un client précis, passer par sa réservation et /v1/bookings/{id} ; pour un export complet, c’est `/fr/admin/clients-export.xlsx` côté Amenitiz. Taux de remplissage constaté : e-mail 94 %, téléphone 94 %, pays 92 %, ville 36 %, adresse 0 %.\n\nSource amont : GET /fr/api/v1/clients.",
        "parameters": [
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/bills": {
      "get": {
        "operationId": "bills",
        "summary": "Les notes émises, paginées (10 par page).",
        "tags": [
          "Facturation"
        ],
        "description": "Source amont : GET /fr/api/v1/bills.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 1
            },
            "description": "Numéro de page."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/price-advisor/policies": {
      "get": {
        "operationId": "price_advisor_policies",
        "summary": "Ce à quoi la propriété a droit côté conseil tarifaire, et sur quel horizon.",
        "tags": [
          "Contexte tarifaire"
        ],
        "description": "Source amont : GET /fr/api/v1/price_advisor/policies.json.",
        "parameters": [
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/events": {
      "get": {
        "operationId": "events",
        "summary": "Les événements et jours fériés susceptibles de peser sur la demande.",
        "tags": [
          "Contexte tarifaire"
        ],
        "description": "L’horizon est borné par l’abonnement : voir `last_day_of_holidays` dans /v1/price-advisor/policies.\n\nSource amont : GET /fr/api/v1/price_advisor/events.json.",
        "parameters": [
          {
            "name": "start_date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Premier jour de la fenêtre (inclus)."
          },
          {
            "name": "end_date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Dernier jour de la fenêtre (exclu pour les nuitées)."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/settings": {
      "get": {
        "operationId": "settings",
        "summary": "Réglages d’affichage du calendrier et régions de jours fériés — utile pour interpréter les couleurs et statuts.",
        "tags": [
          "Contexte tarifaire"
        ],
        "description": "Source amont : GET /fr/api/v1/calendar/settings.json, GET /fr/api/v1/holidays/settings.json.",
        "parameters": [
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/conversations": {
      "get": {
        "operationId": "conversations",
        "summary": "Les conversations voyageurs, et celles qui restent non lues.",
        "tags": [
          "Messagerie"
        ],
        "description": "⚠️ Ces deux endpoints amont vivent sous /api/v1/ SANS préfixe de langue, contrairement à tous les autres.\n\nSource amont : GET /api/v1/messenger/conversations, GET /api/v1/messenger/unread_conversations.",
        "parameters": [
          {
            "name": "unread_only",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ne renvoyer que les non lues."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/grid": {
      "get": {
        "operationId": "grid",
        "summary": "Disponibilité, prix et chambres déjà réservées pour une date — la seule source de la DISPONIBILITÉ.",
        "tags": [
          "Grille"
        ],
        "description": "⚠️ Route COÛTEUSE (~10 s) : la disponibilité n’est exposée par aucune API JSON, elle est lue dans le DOM de la grille tarifaire, que la page doit d’abord rendre. Ne couvre que la fenêtre affichée par Amenitiz (≈ aujourd’hui → +30 j). Une disponibilité à 0 signifie « fermée à la vente », pas « complet ».\n\nSource amont : DOM /fr/admin/planning.",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Jour observé (défaut : aujourd’hui, fuseau Europe/Paris). Valeur par défaut calculée à l’appel."
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = renvoyer la donnée sans l’enveloppe { data, meta }."
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "true = ignorer le cache et réinterroger Amenitiz. À n’utiliser qu’en cas de besoin réel."
          }
        ],
        "responses": {
          "200": {
            "description": "Succès",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          },
          "401": {
            "$ref": "#/components/responses/Erreur"
          },
          "429": {
            "$ref": "#/components/responses/Erreur"
          },
          "503": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/v1/raw/{chemin_amont}": {
      "get": {
        "operationId": "raw",
        "tags": [
          "Passe-plat"
        ],
        "summary": "Relaie tel quel n’importe quel GET de l’API interne Amenitiz.",
        "description": "Le chemin doit commencer par `fr/api/v1`, `fr/api/v2` ou `api/v1`. La chaîne de requête est transmise telle quelle, y compris les paramètres répétés du type `booking_statuses[]`. La réponse est le corps amont, sans enveloppe. C’est la porte de sortie quand une route dédiée manque — et le moyen de continuer à travailler si Amenitiz ajoute un endpoint que cette passerelle ne connaît pas encore.",
        "parameters": [
          {
            "name": "chemin_amont",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "fr/api/v2/property/rooms"
          }
        ],
        "responses": {
          "200": {
            "description": "Corps amont relayé tel quel"
          },
          "400": {
            "$ref": "#/components/responses/Erreur"
          }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "health",
        "tags": [
          "Service"
        ],
        "security": [],
        "summary": "État du service. Public et volontairement avare : ni nom de propriété, ni donnée client.",
        "responses": {
          "200": {
            "description": "État"
          }
        }
      }
    }
  }
}