{
  "openapi": "3.1.0",
  "info": {
    "title": "Immofacile API V2",
    "version": "1.1.0",
    "license": {
      "name": "Proprietary — © Orisha Real Estate",
      "url": "https://www.orisha.com/mentions-legales/"
    },
    "x-logo": {
      "url": "https://public.immo-facile.com/doc-api-v2/logo-orisha.svg",
      "altText": "Orisha Real Estate"
    },
    "description": "# Welcome to the Immofacile API\n\nThis documentation will guide you through integrating with Immofacile, the leading SaaS CRM for real estate agencies. Our REST APIs allow you to connect your applications, automate your business workflows, and extend the power of our ecosystem.\n\n## Target Audience\n\nThis documentation and APIs are designed for three distinct types of users:\n\n- **IT Departments (CIO) of large networks / Franchises:** Who want to interconnect Immofacile with their proprietary internal tools.\n- **Developers / Partners:** Commissioned by our clients to build custom showcase websites or specific business applications.\n- **Software Vendors (PropTech / Ecosystem Partners):** Valuation tools, marketing automation providers, and any type of partner we want to connect to our system.\n- **Real estate listing portals:** Currently fed mostly via CSV or XML feeds, APIs are needed to better manage listing distribution and lead returns.\n\n## 1. API Platform Overview\n\nThe Immofacile API is built on **REST** standards. It uses standard HTTP request methods, explicit HTTP status codes, and a JSON response format.\n\n### 1.1 \"API-First\" Philosophy\n\nTo support the digitalization of real estate agencies, Immofacile offers a bidirectional architecture:\n\n- **Extraction (Read):** Securely access your client data (properties, contacts, events, etc.).\n- **Injection (Write):** Create, update, or enrich the agency database (leads, properties, actions, etc.) while respecting the CRM's business rules.\n\n### 1.2 Environments & Authentication\n\n- **Sandbox (Test):** An isolated environment containing demo data for development without risk of polluting production (coming soon).\n- **Production:** The agency's live environment.\n- **Security:** Authentication relies on the **OAuth 2.0** protocol (via a `Client ID` and `Client Secret` generated from the Immofacile admin dashboard) or by **API Key** for simple internal script integrations.\n\n## 2. Key Concepts & Operating Rules\n\nTo interact effectively with Immofacile, you must understand two core pillars of our architecture: the **Discovery API** for accessing authorized resources, and **Webhooks** for real-time synchronization.\n\n### 2.1 Discovery (Entry Point Discovery)\n\nTo avoid hardcoding URLs and to dynamically adapt to the client agency's access rights, our API implements the **Discovery** principle.\n\n- **The concept:** By querying the single entry point, the API returns the list of all resources you have access to, along with associated URLs and available actions (`GET`, `POST`, etc.).\n- **Example request:** `GET https://api.immofacile.com/v2/`\n\n### 2.2 Webhooks (Real-Time Events)\n\nIntensive polling is prohibited on our platform. To synchronize your data in real time, you must use our **Webhooks** system. Rather than asking Immofacile whether data has changed, Immofacile sends an HTTP `POST` notification to your server as soon as an event occurs.\n\n- **Event format:** Each payload sent by our Webhook contains the event type (`event_type`) and the identifier of the related resource.\n\n## 3. Business Use Cases & Implementation\n\nHere is how to orchestrate our APIs to meet the real-world needs of real estate agents.\n\n### Use Case 1: Create / Update a Lead from an External Tool (Data Write)\n\n- **Step 1:** Duplicate check — Before creating a contact, the third-party application must verify the prospect does not already exist in the agency database via email or phone.\n  - Action: `POST /customers/search` with email criterion\n- **Step 2:** Contact creation — If the contact does not exist, create it.\n  - Action: `POST /customers`\n  - Payload: `{\"firstname\": \"Jean\", \"lastname\": \"Dupont\", \"phone\": \"0601020304\", \"type\": \"Prospect Vendeur\"}`\n- **Step 3:** Search project creation — Create the search project based on the property the contact inquired about.\n- **Step 4:** Add property to contact follow-up — Associate the listing details to the contact so the agent has context during callback.\n\n### Use Case 2: Real-Time Feed for Agency Website / Portal / Partner (Data Read & Webhooks)\n\n- **Step 1:** Initialization (Full Sync) — On first connection, retrieve the entire active catalog.\n- **Step 2:** Listen for updates (Delta Sync via Webhook) — Register the website URL on our webhook to listen for the `property.updated` event.\n  - Action: Configuration via dashboard or `POST /hooks` to subscribe to `property.updated`.\n- **Step 3:** Targeted update — When a price changes or a property is sold in Immofacile, the website receives the webhook. It then queries the specific property to update its local database.\n\n## 4. Support & Escalation\n\n**Having trouble with your integration?**\n\n1. **Check the Logs:** Every error response from our API contains a unique `request_id`.\n2. **Contact dev support:** Send an email to api.imf@orisha.com, always including the `request_id`, the sent payload, and the environment concerned (Sandbox or Prod).\n\n---\n\n## Packs\n\nThe API is organized into packs:\n- **Products** — Property listings management\n- **Customers** — Customer management, search requests, follow-ups, actions\n- **Leads** — Seller and buyer lead creation\n- **Users** — Agent/collaborator management\n- **Agencies** — Agency information\n- **Discovery** — Scope discovery\n- **Webhooks** — Event subscriptions\n\n### Access packs (endpoint badges)\n\nEach endpoint carries one or more colored badges indicating which\nsubscription pack(s) grant access to it. An endpoint may belong to\nseveral packs. Use these badges to check whether your subscription\ncovers a given endpoint.\n\n| Badge | Access pack | Color |\n|-------|-------------|-------|\n| 🟣 **Customers** | Customers, search requests, follow-ups, actions | purple |\n| 🟢 **Properties** | Property listings and criteria | green |\n| 🟠 **Leads** | Seller and buyer lead creation | orange |\n| 🔴 **Sales** | Sale agreements, offers, leases | red |\n| 🟪 **Websites** | Read feeds for websites and portals | blueberry |\n\n## Authentication\n\nObtain a Bearer token via `POST /api/client/token/site` (OAuth2 client credentials).\nInclude it in the `Authorization: Bearer {token}` header for all requests.\n\n**Base URL is dynamic.** The token response includes a `domain` field. Use it\nas the base for every subsequent call (`{domain}/api/v2/site/...`). In the\nTry it console, set the `domain` server variable to that value.\n\n## Error format\n```json\n{ \"error\": { \"code\": \"ERROR_CODE\", \"message\": \"Human-readable message.\" } }\n```\n\n## Pagination\nList endpoints use cursor-based pagination: `next_cursor` + `has_more`.\n\n## Headers\nAll responses include `X-Request-Id` (UUID) for log correlation.\n"
  },
  "servers": [
    {
      "url": "{domain}/api/v2/site",
      "description": "API base URL. The `domain` is not fixed: use the `domain` value returned\nby the token response. In the Try it console, set the `domain` variable to\nthat value before sending requests.\n",
      "variables": {
        "domain": {
          "default": "https://v2.immo-facile.com",
          "description": "Base domain returned in the token response (`domain` field).",
          "enum": [
            "https://v2.immo-facile.com",
            "https://ac3.staging.immo-facile.com",
            "https://ac3.recette1.immo-facile.com"
          ]
        }
      }
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Authentication",
      "description": "OAuth 2.0 token retrieval and API authentication."
    },
    {
      "name": "Products",
      "description": "Property listings management (create, search, read, media, actions)."
    },
    {
      "name": "Criterias",
      "description": "Property and search-request criteria referential (types and allowed values)."
    },
    {
      "name": "Customers",
      "description": "Contact management — details, search, groups, origins, consent, follow-ups, and actions."
    },
    {
      "name": "Leads",
      "description": "Seller and buyer lead creation from external tools."
    },
    {
      "name": "Transactions",
      "description": "Sales agreements, sales offers, and leases."
    },
    {
      "name": "Users",
      "description": "Agent and collaborator management (users, groups, types, genders)."
    },
    {
      "name": "Agencies",
      "description": "Agency information for the authenticated site."
    },
    {
      "name": "Webhooks",
      "description": "Event subscriptions for real-time synchronization."
    },
    {
      "name": "Configs",
      "description": "Configuration referentials such as list values."
    },
    {
      "name": "Agenda",
      "description": "Calendar events, availabilities, permanences, and working time."
    },
    {
      "name": "Changelog",
      "description": "Version history for the Immofacile API V2. Changes follow\nsemantic versioning and the Keep a Changelog format (`Added`, `Changed`,\n`Deprecated`, `Removed`, `Fixed`, `Security`).\n\nThis version number tracks the evolution of the API contract and is\nindependent of the `/v2/` URL prefix, which is a stable interface level\nand does not change with these releases.\n\n## 1.1.0 — 2026-09-16\n\n### Added\n- Richer customer profiles: phone numbers, mandates, client-area access,\n  color, notary, and related contacts.\n- More detail on search requests: criteria, key points (pros/cons),\n  and search areas.\n- Customer addresses and phone numbers can now be written using the same\n  structure returned by the API.\n\n### Changed\n- Response and request keys now use `snake_case` across the whole API.\n\n### Deprecated\n- `camelCase` keys are deprecated in favor of their `snake_case`\n  equivalents. They remain accepted for now (the new key takes\n  precedence when both are sent) and will be removed in a future major\n  release.\n\n## 1.0.0 — 2026-06-17\n\n### Added\n- First public release of the V2 API (REST, OAuth 2.0 authentication).\n- Endpoints for Products, Customers, Leads, Transactions, Users, Agencies, Configs, and Calendar.\n- Webhooks system for real-time synchronization.\n- Discovery endpoint to dynamically discover accessible resources.\n"
    }
  ],
  "x-tagGroups": [
    {
      "name": "API Reference",
      "tags": [
        "Authentication",
        "Products",
        "Criterias",
        "Customers",
        "Leads",
        "Transactions",
        "Users",
        "Agencies",
        "Webhooks",
        "Configs",
        "Agenda"
      ]
    },
    {
      "name": "About",
      "tags": [
        "Changelog"
      ]
    }
  ],
  "paths": {
    "/api/client/token/site": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "obtainSiteToken",
        "summary": "Obtain a site access token",
        "description": "Authenticate using client credentials to obtain a Bearer token scoped to a site.\nThis endpoint is shared between V1 and V2.\nThe token must be included in the `Authorization: Bearer {token}` header for all subsequent requests.\n\n**Authentication:**\n\nPass your credentials in the `Authorization` header using HTTP Basic Auth: `Basic base64(client_id:client_secret)`.\n\n**Request body:**\n\nYou must provide either `site_id` or `manufacturer_id` to scope the token.\n\n**Multi-Agency Scoping:**\n\nAuthentication is linked to a **Site**. A Site groups one or more agencies (manufacturers).\nYour token grants access to all agencies in the Site's `manufacturers_list`.\n\n- The `agency_id` field in requests must reference an agency within that scope.\n- Use `GET /agencies` to list accessible agencies for your token.\n\n**Multi-site scenarios:**\n- Option A: One Site grouping all agencies (single credential, single `site_id`).\n- Option B: Multiple credentials (one per Site), each accessing a subset of agencies. You obtain a separate token for each `site_id`.\n\n**Example (curl):**\n```\ncurl -X POST 'https://v2.immo-facile.com/api/client/token/site' \\\n  -H 'Content-Type: application/x-www-form-urlencoded' \\\n  -H 'Authorization: Basic base64(client_id:client_secret)' \\\n  --data-urlencode 'site_id=12345'\n```\n",
        "security": [
          {
            "basicAuth": []
          }
        ],
        "servers": [
          {
            "url": "https://v2.immo-facile.com",
            "description": "Production (auth endpoint)"
          },
          {
            "url": "https://ac3.staging.immo-facile.com",
            "description": "Staging (auth endpoint)"
          },
          {
            "url": "https://ac3.recette1.immo-facile.com",
            "description": "Recette 1 (auth endpoint)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "oneOf": [
                  {
                    "title": "By site",
                    "type": "object",
                    "required": [
                      "site_id"
                    ],
                    "properties": {
                      "site_id": {
                        "type": "integer",
                        "description": "Numeric site ID to scope the token to.",
                        "example": 12345
                      }
                    }
                  },
                  {
                    "title": "By agency",
                    "type": "object",
                    "required": [
                      "manufacturer_id"
                    ],
                    "properties": {
                      "manufacturer_id": {
                        "type": "integer",
                        "description": "Numeric agency (manufacturer) ID to scope the token to. The site will be resolved from the agency.",
                        "example": 123456
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token issued successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "domain": {
                      "type": "string",
                      "format": "uri",
                      "description": "**Base domain to use for all subsequent API calls.**\nThe API base URL is not fixed: use this value as the base\n(`{domain}/api/v2/site/...`). In the Try it console, paste it\ninto the `domain` server variable.\n",
                      "example": "https://v2.immo-facile.com"
                    },
                    "token_type": {
                      "type": "string",
                      "enum": [
                        "Bearer"
                      ]
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Token lifetime in seconds",
                      "example": 604800
                    },
                    "access_token": {
                      "type": "string",
                      "description": "Bearer token to use in the Authorization header for all subsequent API calls."
                    },
                    "refresh_token": {
                      "type": "string",
                      "description": "Token used to obtain a new access token once the current one expires."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials or missing Authorization header"
          },
          "403": {
            "description": "Site or manufacturer not in scope for this client"
          },
          "404": {
            "description": "Site or manufacturer not found"
          },
          "422": {
            "description": "Validation error (missing site_id or manufacturer_id)"
          }
        }
      }
    },
    "/discovery": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getDiscovery",
        "summary": "Get accessible scope",
        "description": "Returns all agencies and sites accessible by the authenticated client.\n\nThis is the recommended **first call** when integrating. It tells you:\n- Which agencies (manufacturers) your token has access to\n- The site each agency belongs to\n- Basic agency information (name, address, SIRET, etc.)\n\nUse the returned `agency_id` values in subsequent API calls (e.g. `POST /products`,\n`POST /leads/sellers`) to specify which agency a resource belongs to.\n",
        "responses": {
          "200": {
            "description": "List of accessible agencies",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DiscoveryAgency"
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "site_id": 100,
                      "agency_id": 123,
                      "name": "Agence Centre Ville",
                      "legal_name": "SAS Immobilier Centre",
                      "address": "15 rue de la République",
                      "postal_code": "69001",
                      "city": "Lyon",
                      "email": "centre@agence.fr",
                      "phone": "+33472123456",
                      "siret": "12345678901234",
                      "siren": "123456789"
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/agencies": {
      "get": {
        "tags": [
          "Agencies"
        ],
        "operationId": "listAgencies",
        "summary": "List agencies",
        "description": "Returns all agencies accessible by the authenticated token with cursor-based pagination.",
        "parameters": [
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of agencies",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AgencyResponse"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/CursorPaginationMeta"
                    },
                    "links": {
                      "$ref": "#/components/schemas/CursorPaginationLinks"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/agencies/{agencyId}": {
      "get": {
        "tags": [
          "Agencies"
        ],
        "operationId": "getAgency",
        "summary": "Get agency details",
        "description": "Returns detailed information about a specific agency, including its users.",
        "parameters": [
          {
            "name": "agencyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "format",
            "in": "query",
            "description": "Response format. Use `tag_document` for a simplified format used in document generation.",
            "schema": {
              "type": "string",
              "enum": [
                "tag_document"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Agency details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AgencyFullResponse"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/products": {
      "post": {
        "tags": [
          "Products"
        ],
        "operationId": "createProduct",
        "summary": "Create a product",
        "x-badges": [
          {
            "name": "Properties",
            "color": "green"
          }
        ],
        "description": "Creates a new product (property listing).\n\n**Criteria (`criteria` field):**\n- Each criterion is an object with `id` (criteria identifier) and `value`.\n- The `id` accepts **either** the XML identifier (e.g. `\"TypeBien\"`, `\"NbPieces\"`, `\"Surface\"`) **or** the numeric ID (e.g. `\"27\"`). Both are resolved by the validator.\n- Value format depends on criteria type: string for UNIQUE, number for NUMBER, boolean 0/1 for FLAG, array for MULTIPLE, ISO date for DATE.\n- For UNIQUE/MULTIPLE types, the value must exactly match a valid **value code** (`model` field) from the referential. Use `GET /criterias/product/{id}/values` to discover valid values.\n\n**⚠️ Important — UNIQUE criteria values:**\nThe `value` for UNIQUE criteria must be the **text code** (the `model` field from the\nvalues endpoint), NOT the numeric ID. For example:\n- ✅ Correct: `{\"id\": \"TypeBien\", \"value\": \"Appartement\"}`\n- ❌ Wrong: `{\"id\": \"TypeBien\", \"value\": \"1\"}` (this is the value ID, not the code)\n\nThe values endpoint (`GET /criterias/product/{id}/values`) returns objects like:\n```json\n{ \"id\": 1, \"model\": \"Appartement\", \"label\": \"Appartement\" }\n```\nYou must use the `model` field as the `value` in your criteria array.\n\n**Product status (`Statut` criterion):**\n- The product is only **active and visible** in the interface when `Statut = \"EnCours\"`.\n- If you do NOT send the `Statut` criterion, the system applies the site's default\n  value (configured per site). This default is usually `\"EnCours\"` (active), but some\n  sites may have a different default — in that case the product may not be immediately\n  visible in the interface.\n- **Recommendation:** Always explicitly include `{\"id\": \"Statut\", \"value\": \"EnCours\"}`\n  in your criteria array to ensure the product is immediately active.\n\n**DPE (Energy Performance):**\n- Send numeric values via `conso_energ` (kWh/m²/year) and `valeur_GES` (kgCO₂/m²/year).\n- The system **automatically calculates** the DPE/GES letter (A-G) from numerical values based on property type and country.\n- If you already know the letter, send it directly via `ConsoEner` / `GES` criteria instead.\n\n**Geolocation (GPS):**\n- If the `Alentour` criterion is provided (format `\"lat,lng\"`, e.g. `\"48.8566,2.3522\"`), it is used directly as the property's GPS coordinates.\n- If `Alentour` is NOT provided, the system **automatically geocodes** from `Adresse` + `CodePostal` + `Ville` criteria. Fallback: geocodes from city+postal code only.\n\n**Model (reference):**\n- The `model` field is the property's internal reference number (mandate number, agency reference).\n- If omitted, the system auto-generates the next available reference for the agency.\n- You can use it to store your own identifier for cross-referencing.\n\n**Rooms (`rooms` field):**\n- Each room has a `type` (validated against referential list 1), optional `surface`, `niveau`, `exposition` (list 2), `vue` (list 3), `sol` (list 4), `commentaire`.\n- Use `GET /configs/listes/{listeId}/values` to discover valid values (1=room types, 2=expositions, 3=views, 4=floor types).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProductRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Product created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/products/search": {
      "post": {
        "tags": [
          "Products"
        ],
        "operationId": "searchProducts",
        "summary": "Search products",
        "x-badges": [
          {
            "name": "Properties",
            "color": "green"
          },
          {
            "name": "Websites",
            "color": "blueberry"
          }
        ],
        "description": "Searches products by criteria with cursor-based or offset-based pagination.\nRequires `SITE_PRODUCT:READ` permission and `RS:PRODUCT_SEARCH` right.\n\n**Pagination modes:**\n- **Cursor-based (recommended):** Provide `cursor` parameter. Returns `next_cursor`\n  for the next page. Order is by `products_id ASC`.\n- **Offset-based (legacy):** Provide `skip` + `count`. Returns up to 2000 IDs.\n\n**Response format:**\n- Without `?fetch=`: returns only product IDs (lightweight).\n- With `?fetch=criteres_text,descriptions,...`: returns hydrated product objects\n  with the requested related data.\n\n**Filtering by user (agent):**\nUse `products_admin_id` to filter by assigned agent. Accepts an array of IDs\nor a comma-separated string.\n\n**Criteria operators:**\n| Operator | Description |\n|----------|-------------|\n| `EGAL` | Equals |\n| `DIFFERENT` | Not equal |\n| `SUPERIEUR` | Greater than or equal |\n| `INFERIEUR` | Less than or equal |\n| `CONTIENT` | Contains (multi-value text) |\n| `COMPREND` | Includes (array contains) |\n| `NE_COMPREND_PAS` | Does not include |\n",
        "parameters": [
          {
            "name": "fetch",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of additional related data to include for each product.\nOnly applies when results are hydrated (not for ID-only responses).\n\n**Available values:**\n- `criteres_text` — Text criteria values\n- `criteres_number` — Numeric criteria values\n- `criteres_fulltext` — Full-text criteria\n- `criteres_flag` — Boolean criteria\n- `criteres_publication_errors` — Criteria blocking publication\n- `descriptions` — Web descriptions (multilingual)\n- `rooms` — Room details\n- `products_photos` — Photo URLs\n- `customer` — Seller/owner contact\n- `assigned_to` — Assigned agent\n- `created_by` — Creator agent\n- `publications` — Publication/distribution status\n- `actions_history` — Action log\n- `sale_agreement` — Related compromis\n- `active_sale_agreement` — Active compromis only\n- `log_actions` — Action log entries\n- `live_visit` — Live visit data\n- `scopes` — Permission scopes\n- `statistic` — Statistics data\n- `insee` — INSEE code\n- `online_booking` — Online booking config\n- `category` — Product category\n- `themes` — Associated themes\n",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "example": [
              "criteres_text",
              "criteres_number",
              "descriptions",
              "products_photos",
              "customer"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "criterias": {
                    "type": "array",
                    "description": "Filter criteria. Each item defines a condition on a product criterion.\nUse `GET /criterias/product/all` to discover available criteria IDs and types.\n\n**Note:** This field is named `criterias` on the search endpoint.\nCreate (`POST /products`) and update (`PATCH /products/{id}`) use `criteria` (without \"s\").\n",
                    "items": {
                      "type": "object",
                      "required": [
                        "id",
                        "operator",
                        "value"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Criteria identifier. Accepts either the XML key (e.g. `\"TypeBien\"`)\nor the numeric ID (e.g. `\"27\"`).\n",
                          "example": "TypeBien"
                        },
                        "operator": {
                          "type": "string",
                          "enum": [
                            "EGAL",
                            "DIFFERENT",
                            "SUPERIEUR",
                            "INFERIEUR",
                            "CONTIENT",
                            "COMPREND",
                            "NE_COMPREND_PAS"
                          ],
                          "description": "Comparison operator.",
                          "example": "EGAL"
                        },
                        "value": {
                          "description": "Criteria value. Format depends on criteria type and operator.\nCan be a string or an array of strings (for `CONTIENT`/`COMPREND` operators).\n",
                          "example": "Appartement"
                        }
                      }
                    }
                  },
                  "keyword": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Free-text search. Matches against product reference, address+city,\nor owner name. For numeric values, also matches price range (±10%).\n",
                    "example": "Dupont"
                  },
                  "model": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Filter by exact product reference.",
                    "example": "REF-2025-001"
                  },
                  "customer_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Filter by owner/seller customer ID.",
                    "example": 789
                  },
                  "agency_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Filter by agency ID. Must be within the authenticated site's scope.\n",
                    "example": 123
                  },
                  "products_admin_id": {
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      },
                      {
                        "type": "string"
                      }
                    ],
                    "description": "Filter by assigned user (agent) ID(s). Accepts either:\n- An array of user IDs: `[456, 789]`\n- A comma-separated string: `\"456,789\"`\n\nFilters products where the assigned agent matches one of the provided IDs.\nUse `GET /users` to discover available user IDs.\n",
                    "example": [
                      456,
                      789
                    ]
                  },
                  "last_modified": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "Filter products modified on or after this date.\nFormat: ISO 8601 (`YYYY-MM-DDTHH:mm:ss+HH:MM`).\n",
                    "example": "2026-01-01T00:00:00+01:00"
                  },
                  "status_web": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "Filter by web publication status:\n- `true` — Only published products\n- `false` — Only unpublished products\n"
                  },
                  "hasVisit360": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "Filter by 360° virtual visit availability:\n- `true` — Only products with a virtual visit\n- `false` — Only products without\n"
                  },
                  "sort_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "id",
                      "prix",
                      "date",
                      "surface",
                      "date_added"
                    ],
                    "description": "Field to sort results by:\n- `id` — Product ID (default)\n- `prix` — Price\n- `date` — Last modification date\n- `surface` — Living area\n- `date_added` — Creation date\n"
                  },
                  "sort_order": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "asc",
                      "desc"
                    ],
                    "description": "Sort direction. Default `desc`.",
                    "default": "desc"
                  },
                  "cursor": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Cursor for pagination (base64-encoded last product ID from previous\nresponse's `next_cursor`). Omit for the first page.\nWhen `cursor` is provided, `skip`/`count` are ignored.\n"
                  },
                  "count": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "maximum": 2000,
                    "description": "Number of results per page (offset-based mode). Maximum 2000 for\nID-only responses, 100 when `?fetch=` is used. Ignored when `cursor` is provided.\n",
                    "default": 100
                  }
                }
              },
              "example": {
                "criterias": [
                  {
                    "id": "TypeBien",
                    "operator": "EGAL",
                    "value": "Appartement"
                  },
                  {
                    "id": "Prix",
                    "operator": "INFERIEUR",
                    "value": "400000"
                  },
                  {
                    "id": "Surface",
                    "operator": "SUPERIEUR",
                    "value": "50"
                  }
                ],
                "products_admin_id": [
                  456
                ],
                "sort_type": "date",
                "sort_order": "desc",
                "cursor": null
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Search results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProductSummary"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/products/search/count": {
      "post": {
        "tags": [
          "Products"
        ],
        "operationId": "countProductSearch",
        "summary": "Count search results",
        "x-badges": [
          {
            "name": "Properties",
            "color": "green"
          },
          {
            "name": "Websites",
            "color": "blueberry"
          }
        ],
        "description": "Returns the number of products matching the given criteria.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "criterias": {
                    "type": "array",
                    "description": "Same filter criteria as `POST /products/search`.\n",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "operator": {
                          "type": "string"
                        },
                        "value": {}
                      }
                    }
                  },
                  "keyword": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "model": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "customer_id": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "agency_id": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "products_admin_id": {
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      },
                      {
                        "type": "string"
                      }
                    ],
                    "description": "Filter by assigned user (agent) ID(s)."
                  },
                  "last_modified": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "status_web": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "hasVisit360": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Count result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/products/{productId}": {
      "get": {
        "tags": [
          "Products"
        ],
        "operationId": "getProduct",
        "summary": "Get a product",
        "x-badges": [
          {
            "name": "Properties",
            "color": "green"
          },
          {
            "name": "Sales",
            "color": "red"
          },
          {
            "name": "Websites",
            "color": "blueberry"
          }
        ],
        "description": "Returns detailed information about a product.\nRequires `SITE_PRODUCT:READ` permission and `RS:PRODUCT_GET` right.\n\nUse `?fetch=` to include additional related data (same values as search endpoint).\nWithout `?fetch=`, returns the base product fields via the standard resource.\n",
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "description": "Unique identifier of the product.",
            "schema": {
              "type": "integer"
            },
            "example": 12345
          },
          {
            "name": "fetch",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of additional data to include\n(e.g. `criteres_text,descriptions,products_photos`).\nSame values as the search endpoint's `?fetch=` parameter.\n",
            "schema": {
              "type": "string"
            },
            "example": "criteres_text,criteres_number,descriptions,rooms,products_photos"
          }
        ],
        "responses": {
          "200": {
            "description": "Product details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Product"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Products"
        ],
        "operationId": "updateProduct",
        "summary": "Update a product",
        "x-badges": [
          {
            "name": "Properties",
            "color": "green"
          }
        ],
        "description": "Partially updates a product. Only provided fields are modified.\nRequires `SITE_PRODUCT:WRITE` permission and `RS:PRODUCT_UPDATE` right.\n\n**Important:** The request body must include `product_id` (same value as the\npath parameter) — used for scope verification.\n\n**Criteria:** When `criteria` is provided, only the specified criteria are updated.\nExisting criteria not included in the array are preserved (not reset).\n\n**Rooms:** When `rooms` is provided, rooms can be updated by including their `id`,\nor new rooms can be added without an `id`. In update mode, `vue` and `sol` are\nmutually exclusive on a given room.\n\n**Additional updatable fields:**\n- `model` — Change product reference\n- `agency_id` — Transfer to another agency (must remain in scope)\n- `user_id` — Change assigned agent\n- `web_status` — Enable/disable web publishing (boolean)\n- `intercabinet` — Enable/disable inter-agency sharing (boolean)\n- `seller_statistics` — Enable/disable seller statistics (boolean)\n- `next_contact` — Next follow-up date (format `YYYY-MM-DD`)\n- `color` — Color code (1-5)\n",
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "description": "Unique identifier of the product to update.",
            "schema": {
              "type": "integer"
            },
            "example": 12345
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProductRequest"
              },
              "example": {
                "product_id": 12345,
                "criteria": [
                  {
                    "id": "Prix",
                    "value": "320000"
                  },
                  {
                    "id": "Surface",
                    "value": "92"
                  }
                ],
                "web_status": true
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Updated successfully"
          },
          "400": {
            "description": "Invalid criteria. Response contains details about which criteria\nvalue was not recognized.\n"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/products/{productId}/criteria": {
      "get": {
        "tags": [
          "Products"
        ],
        "operationId": "getProductCriteriaValues",
        "summary": "Get product criteria",
        "x-badges": [
          {
            "name": "Properties",
            "color": "green"
          }
        ],
        "description": "Returns the criteria values for a product.",
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Product criteria",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Criteria ID"
                          },
                          "value": {
                            "description": "Criteria value (type depends on criteria)"
                          },
                          "label": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/products/{productId}/lots": {
      "get": {
        "tags": [
          "Products"
        ],
        "operationId": "getProductLots",
        "summary": "Get product lots",
        "x-badges": [
          {
            "name": "Properties",
            "color": "green"
          },
          {
            "name": "Websites",
            "color": "blueberry"
          }
        ],
        "description": "Returns the lots associated with a product (sub-products, e.g. units in a building).",
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "fetch",
            "in": "query",
            "description": "Additional data to include (comma-separated)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Product lots",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProductListItem"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/products/{productId}/pictures/public": {
      "post": {
        "tags": [
          "Products"
        ],
        "operationId": "addProductPublicPhoto",
        "summary": "Add a public photo",
        "x-badges": [
          {
            "name": "Properties",
            "color": "green"
          }
        ],
        "description": "Uploads a public photo for a product. Minimum width 800px, max 20MB.",
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Photo added successfully"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/products/{productId}/pictures/private": {
      "post": {
        "tags": [
          "Products"
        ],
        "operationId": "addProductPrivatePhoto",
        "summary": "Add a private photo",
        "x-badges": [
          {
            "name": "Properties",
            "color": "green"
          }
        ],
        "description": "Uploads a private photo for a product. Minimum width 800px, max 20MB.",
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Photo added successfully"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/products/{productId}/actions": {
      "get": {
        "tags": [
          "Products"
        ],
        "operationId": "listProductActions",
        "summary": "List actions for a product",
        "description": "Returns paginated action history for a product.",
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of actions"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Products"
        ],
        "operationId": "createProductAction",
        "summary": "Create an action for a product",
        "description": "Logs a new action on a product record.\n\n**Important:** The action is always recorded in the application log (journal). However, it will only appear in the product's action history tab if the action type has child actions configured for the site. This depends on the agency's action workflow configuration.\n\nTo determine if an action type has child actions (and will therefore appear in the product history), check the action type configuration in the back-office or use `GET /actions/types` to list available types.\n",
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action_id"
                ],
                "properties": {
                  "action_id": {
                    "type": "integer",
                    "description": "Action type ID (must be active for the site)"
                  },
                  "user_id": {
                    "type": "integer",
                    "description": "User (agent) ID who performed the action"
                  },
                  "result": {
                    "type": "string",
                    "description": "Action comment/result"
                  },
                  "date_performed": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Action created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "description": "Product ID"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/criterias/product/all": {
      "get": {
        "tags": [
          "Criterias"
        ],
        "operationId": "listProductCriteria",
        "summary": "Get all product criteria",
        "x-badges": [
          {
            "name": "Leads",
            "color": "orange"
          },
          {
            "name": "Properties",
            "color": "green"
          },
          {
            "name": "Customers",
            "color": "purple"
          },
          {
            "name": "Websites",
            "color": "blueberry"
          }
        ],
        "description": "Returns all product criteria definitions for the site, including type,\nlabel, activation state and personalisation flags.\nRequires `SITE_CONFIG` permission and `RS:CRITERE_PRODUCT_ALL` right.\n\n**Criteria types:**\n| Type | Description | Value format |\n|------|-------------|--------------|\n| `UNIQUE` | Single choice from predefined values | value code string |\n| `NUMBER` | Numeric value | Number (int or float) |\n| `FLAG` | Boolean | `0` or `1` |\n| `TEXT` | Free text | String |\n| `MULTIPLE` | Multiple choice from predefined values | Array of value code strings |\n| `DATE` | Date/datetime | ISO 8601 or `dd/MM/yyyy` |\n\nUse `GET /criterias/product/{id}/values` to get the valid values for\nUNIQUE/MULTIPLE type criteria.\n\n**Common criteria examples:**\n| XML Key | Type | Description |\n|---------|------|-------------|\n| `TypeBien` | UNIQUE | Property type (Appartement, Maison, Terrain...) |\n| `TypeTransaction` | UNIQUE | Transaction type (Vente, Location...) |\n| `NbPieces` | NUMBER | Number of rooms |\n| `Surface` | NUMBER | Living area in m² |\n| `Prix` | NUMBER | Price |\n| `conso_energ` | NUMBER | Energy consumption (kWh/m²/year) |\n| `ConsoEner` | UNIQUE | DPE letter (A-G, auto-calculated from conso_energ) |\n| `Adresse` | TEXT | Street address |\n| `CodePostal` | TEXT | Postal code |\n| `Ville` | TEXT | City |\n| `Alentour` | TEXT | GPS coordinates (`\"lat,lng\"`) |\n",
        "responses": {
          "200": {
            "description": "List of product criteria",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CriteriaResource"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/criterias/product/{criteriaId}": {
      "get": {
        "tags": [
          "Criterias"
        ],
        "operationId": "getProductCriteria",
        "summary": "Get a product criteria by ID",
        "x-badges": [
          {
            "name": "Leads",
            "color": "orange"
          },
          {
            "name": "Properties",
            "color": "green"
          },
          {
            "name": "Customers",
            "color": "purple"
          },
          {
            "name": "Websites",
            "color": "blueberry"
          }
        ],
        "description": "Returns a single product criteria definition with its possible values.\nRequires `SITE_CONFIG` permission and `RS:CRITERE_PRODUCT` right.\n",
        "parameters": [
          {
            "name": "criteriaId",
            "in": "path",
            "required": true,
            "description": "Criteria ID (numeric).",
            "schema": {
              "type": "integer"
            },
            "example": 27
          }
        ],
        "responses": {
          "200": {
            "description": "Criteria details with values",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CriteriaResource"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/criterias/search-requests": {
      "get": {
        "tags": [
          "Criterias"
        ],
        "operationId": "listSearchRequestCriteria",
        "summary": "Get search request criteria",
        "x-badges": [
          {
            "name": "Customers",
            "color": "purple"
          }
        ],
        "description": "Returns the criteria available for buyer search requests on this site.\nRequires `SITE_CONFIG` permission and `RS:CRITERE_SR_LIST` right.\n\nUse these criteria IDs when creating search requests via `POST /leads/buyers`\nor `POST /customers/{id}/search-requests`.\n",
        "responses": {
          "200": {
            "description": "List of search request criteria",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CriteriaResource"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/criterias/zone-list": {
      "get": {
        "tags": [
          "Criterias"
        ],
        "operationId": "getZoneList",
        "summary": "Get zone list",
        "description": "Returns the list of geographic zones available for the site.\nRequires `SITE_CONFIG` permission and `RS:CRITERE_ZONE_LIST` right.\n\nZones are used in search request criteria (typically criterion ID 30)\nto filter properties by location.\nResponses are cached with ETag.\n",
        "parameters": [
          {
            "name": "manufacturer_id",
            "in": "query",
            "required": false,
            "description": "Optional agency ID to get the zone list for a different site context.\nMust be within the authenticated scope.\n",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of zones",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "example": [
                  "Paris 1er",
                  "Paris 2ème",
                  "Lyon 1er",
                  "Lyon 2ème"
                ]
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/criterias/categories/{categoryId}": {
      "get": {
        "tags": [
          "Criterias"
        ],
        "operationId": "getCriteriaByCategory",
        "summary": "Get criteria by category",
        "description": "Returns the criteria belonging to a given product category.\nRequires `SITE_CONFIG` permission and `RS:CRITERE_CATEGORY` right.\n\nCategories group criteria by property type (e.g. criteria specific to\napartments vs. houses vs. land).\n",
        "parameters": [
          {
            "name": "categoryId",
            "in": "path",
            "required": true,
            "description": "Category ID.",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Criteria in the category",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CriteriaResource"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/customers": {
      "post": {
        "tags": [
          "Customers"
        ],
        "operationId": "createCustomer",
        "summary": "Create a customer",
        "x-badges": [
          {
            "name": "Customers",
            "color": "purple"
          }
        ],
        "description": "Creates a new customer.\nRequires `SITE_CUSTOMER:WRITE` permission and `RS:CUSTOMER_CREATE` right.\n\n**Duplicate handling:**\n- The system checks for duplicates by **email OR phones** (any match on the same site).\n- Without `check_duplicate`: if a duplicate is found, the existing customer is **silently\n  updated** with the provided data and its ID is returned (HTTP 201).\n- With `check_duplicate: true`: returns HTTP 409 if a duplicate is found.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCustomerRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Customer created (or updated if duplicate found without checkDuplicate)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "description": "ID of the created or existing customer."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": 789
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Duplicate customer detected (only when `checkDuplicate: true`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "CONFLICT",
                    "message": "Duplicate customer found."
                  }
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/customers/search": {
      "post": {
        "tags": [
          "Customers"
        ],
        "operationId": "searchCustomers",
        "summary": "Search customers",
        "x-badges": [
          {
            "name": "Customers",
            "color": "purple"
          }
        ],
        "description": "Searches customers with cursor-based pagination.\nRequires `SITE_CUSTOMER:READ` permission and `RS:CUSTOMER_SEARCH` right.\n\nResults are ordered by `customers_id` descending (most recent first).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchCustomerRequest"
              },
              "example": {
                "email": "dupont@example.com",
                "per_page": 25
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Paginated search results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerResponse"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/CursorPaginationMeta-2"
                    },
                    "links": {
                      "$ref": "#/components/schemas/CursorPaginationLinks-2"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/customers/groups": {
      "get": {
        "tags": [
          "Customers"
        ],
        "operationId": "listCustomerGroups",
        "summary": "List customer groups",
        "x-badges": [
          {
            "name": "Customers",
            "color": "purple"
          }
        ],
        "description": "Returns all available customer groups for the site.\nRequires `SITE_CUSTOMER:READ` permission.\n\nGroups categorize customers (e.g. \"Demandeur\", \"Propriétaire\", \"Locataire\").\nUse group IDs when creating customers or leads.\n",
        "responses": {
          "200": {
            "description": "List of groups",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerGroupResponse"
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": 1,
                      "name": "Demandeur"
                    },
                    {
                      "id": 2,
                      "name": "Propriétaire vendeur"
                    },
                    {
                      "id": 4,
                      "name": "Propriétaire"
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/customers/origins": {
      "get": {
        "tags": [
          "Customers"
        ],
        "operationId": "listCustomerOrigins",
        "summary": "List customer origins",
        "x-badges": [
          {
            "name": "Customers",
            "color": "purple"
          }
        ],
        "description": "Returns all available customer origins for the site.\nRequires `SITE_CUSTOMER:READ` permission.\n\nOrigins track how a customer entered the system (e.g. \"Site internet\", \"Portail\",\n\"Recommandation\"). Use origin IDs when creating customers or leads.\n",
        "responses": {
          "200": {
            "description": "List of origins",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerOriginResponse"
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": 12,
                      "label": "Site internet"
                    },
                    {
                      "id": 15,
                      "label": "Portail immobilier"
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/customers/{customerId}": {
      "get": {
        "tags": [
          "Customers"
        ],
        "operationId": "getCustomer",
        "summary": "Get a customer",
        "x-badges": [
          {
            "name": "Customers",
            "color": "purple"
          },
          {
            "name": "Properties",
            "color": "green"
          }
        ],
        "description": "Returns detailed customer information.\nRequires `SITE_CUSTOMER:READ` permission and `RS:CUSTOMER_GET` right.\n\nUse `include` to load additional relations.\n\n**Available includes:**\n`address`, `groups`, `color`, `origin`, `user`,\n`gender`, `stopSms`, `notary`, `relations`, `subscriptions`,\n`searchRequests`, `agency`, `consent`, `mandate`, `website`.\n",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "Unique identifier of the customer.",
            "schema": {
              "type": "integer"
            },
            "example": 789
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of relations to include in the response.\nEach include triggers additional data loading.\n",
            "schema": {
              "type": "string"
            },
            "example": "address,groups,origin,user"
          }
        ],
        "responses": {
          "200": {
            "description": "Customer details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CustomerResponse"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Customers"
        ],
        "operationId": "updateCustomer",
        "summary": "Update a customer",
        "x-badges": [
          {
            "name": "Customers",
            "color": "purple"
          }
        ],
        "description": "Partially updates a customer. Only provided fields are modified.\nRequires `SITE_CUSTOMER:WRITE` permission and `RS:CUSTOMER_UPDATE` right.\n",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "Unique identifier of the customer to update.",
            "schema": {
              "type": "integer"
            },
            "example": 789
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCustomerRequest"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Updated successfully"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/customers/{customerId}/consent": {
      "post": {
        "tags": [
          "Customers"
        ],
        "operationId": "upsertCustomerConsent",
        "summary": "Create or update cold-calling consent",
        "x-badges": [
          {
            "name": "Customers",
            "color": "purple"
          }
        ],
        "description": "Creates or updates the cold-calling consent (consentement anti-démarchage)\nfor a customer.\nRequires `SITE_CUSTOMER:WRITE` permission and `RS:CUSTOMER_UPDATE` right.\n\n**Behavior:**\n- One consent record per customer (upsert).\n- If the customer previously had their consent revoked, calling this endpoint\n  reactivates it (clears the revocation date).\n- Proof files (`proofs`) are uploaded to the customer's cloud storage under\n  `Documents confidentiels/Consentement` subfolder.\n\n**Content-Type:** Use `multipart/form-data` when uploading proof files,\nor `application/json` when no files are attached.\n",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "Unique identifier of the customer.",
            "schema": {
              "type": "integer"
            },
            "example": 789
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCustomerConsentRequest"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCustomerConsentRequest"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Consent created or updated successfully"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      },
      "delete": {
        "tags": [
          "Customers"
        ],
        "operationId": "revokeCustomerConsent",
        "summary": "Revoke cold-calling consent",
        "x-badges": [
          {
            "name": "Customers",
            "color": "purple"
          }
        ],
        "description": "Revokes the cold-calling consent for a customer.\nRequires `SITE_CUSTOMER:WRITE` permission and `RS:CUSTOMER_UPDATE` right.\n\n**Behavior:**\n- Sets a revocation date on the consent record.\n- Deletes all associated proof files from storage.\n- If no consent exists, the operation still returns 204.\n",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "Unique identifier of the customer.",
            "schema": {
              "type": "integer"
            },
            "example": 789
          }
        ],
        "responses": {
          "204": {
            "description": "Consent revoked successfully"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/customers/{customerId}/search-requests": {
      "post": {
        "tags": [
          "Customers"
        ],
        "operationId": "createSearchRequest",
        "summary": "Create a search request",
        "x-badges": [
          {
            "name": "Customers",
            "color": "purple"
          }
        ],
        "description": "Creates a search request for a customer with criteria.\nRequires `SITE_CUSTOMER:WRITE` permission.\n\nSearch requests define what a buyer/tenant is looking for. When active, the system\nruns automatic matching against new property listings.\n",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "ID of the customer to attach the search request to.",
            "schema": {
              "type": "integer"
            },
            "example": 789
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSearchRequestBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Search request created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "description": "ID of the created search request."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": 42
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/customers/{customerId}/search-requests/{searchRequestId}": {
      "patch": {
        "tags": [
          "Customers"
        ],
        "operationId": "updateSearchRequest",
        "summary": "Update a search request",
        "x-badges": [
          {
            "name": "Customers",
            "color": "purple"
          }
        ],
        "description": "Partially updates a search request for a customer.\nRequires `SITE_CUSTOMER:WRITE` permission.\n\nOnly provided fields are modified. Criteria are replaced entirely when provided\n(not merged with existing ones).\n",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "ID of the customer.",
            "schema": {
              "type": "integer"
            },
            "example": 789
          },
          {
            "name": "searchRequestId",
            "in": "path",
            "required": true,
            "description": "ID of the search request to update.",
            "schema": {
              "type": "integer"
            },
            "example": 42
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSearchRequestBody"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Search request updated"
          },
          "400": {
            "description": "Invalid criteria"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/customers/{customerId}/follow-ups": {
      "get": {
        "tags": [
          "Customers"
        ],
        "operationId": "listCustomerFollowUps",
        "summary": "List follow-ups",
        "x-badges": [
          {
            "name": "Customers",
            "color": "purple"
          }
        ],
        "description": "Returns paginated follow-ups (rapprochements) for a customer.\nRequires `SITE_CUSTOMER:READ` permission.\n\nA follow-up links a customer to a product they are interested in,\ntracking the progression of the relationship.\n",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "ID of the customer.",
            "schema": {
              "type": "integer"
            },
            "example": 789
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Number of results per page. Maximum 200, default 25.\n",
            "schema": {
              "type": "integer",
              "default": 25,
              "maximum": 200
            },
            "example": 50
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of follow-ups",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FollowUpResponse"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/CursorPaginationMeta-2"
                    },
                    "links": {
                      "$ref": "#/components/schemas/CursorPaginationLinks-2"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/customers/{customerId}/follow-ups/{productId}": {
      "post": {
        "tags": [
          "Customers"
        ],
        "operationId": "createCustomerFollowUp",
        "summary": "Create a follow-up",
        "x-badges": [
          {
            "name": "Customers",
            "color": "purple"
          }
        ],
        "description": "Creates a follow-up linking a customer to a product.\nReturns 409 if the follow-up already exists (customer already follows this product).\nRequires `SITE_CUSTOMER:WRITE` permission.\n",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "ID of the customer.",
            "schema": {
              "type": "integer"
            },
            "example": 789
          },
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "description": "ID of the product to follow.",
            "schema": {
              "type": "integer"
            },
            "example": 12345
          }
        ],
        "responses": {
          "201": {
            "description": "Follow-up created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FollowUpResponse"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Follow-up already exists (customer already linked to this product)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FollowUpResponse"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Customers"
        ],
        "operationId": "updateCustomerFollowUp",
        "summary": "Update a follow-up",
        "x-badges": [
          {
            "name": "Customers",
            "color": "purple"
          }
        ],
        "description": "Updates a follow-up (e.g. rating).\nRequires `SITE_CUSTOMER:WRITE` permission.\n",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "ID of the customer.",
            "schema": {
              "type": "integer"
            },
            "example": 789
          },
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "description": "ID of the followed product.",
            "schema": {
              "type": "integer"
            },
            "example": 12345
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateFollowUpRequest"
              },
              "example": {
                "rating": 4
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Follow-up updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FollowUpResponse"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "delete": {
        "tags": [
          "Customers"
        ],
        "operationId": "deleteCustomerFollowUp",
        "summary": "Delete a follow-up",
        "x-badges": [
          {
            "name": "Customers",
            "color": "purple"
          }
        ],
        "description": "Removes a follow-up between a customer and a product.\nRequires `SITE_CUSTOMER:WRITE` permission.\n",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "ID of the customer.",
            "schema": {
              "type": "integer"
            },
            "example": 789
          },
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "description": "ID of the product to unfollow.",
            "schema": {
              "type": "integer"
            },
            "example": 12345
          }
        ],
        "responses": {
          "204": {
            "description": "Follow-up deleted"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/customers/{customerId}/actions": {
      "get": {
        "tags": [
          "Customers"
        ],
        "operationId": "listCustomerActions",
        "summary": "List actions for a customer",
        "description": "Returns paginated action history for a customer.\nRequires `SITE_CUSTOMER:READ` permission.\n\nActions are commercial activities logged on a customer (calls, visits,\nemails, etc.). Uses offset-based pagination with `cursor` parameter.\n",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "ID of the customer.",
            "schema": {
              "type": "integer"
            },
            "example": 789
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Offset for pagination (number of items to skip). Default 0.\n",
            "schema": {
              "type": "integer",
              "default": 0
            },
            "example": 0
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of actions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ActionResponse"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "Next offset value, or null if no more results."
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "Whether more results are available."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Customers"
        ],
        "operationId": "createCustomerAction",
        "summary": "Create an action for a customer",
        "description": "Logs a new commercial action on a customer record.\nRequires `SITE_CUSTOMER:WRITE` permission.\n\n**Important:** The action is always recorded in the application journal.\nHowever, it will only appear in the customer's action history tab if the\naction type has child actions configured for the site. This depends on\nthe agency's action workflow configuration.\n\nUse `GET /actions/types` to list available action types and check which\nones have child actions configured.\n",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "description": "ID of the customer.",
            "schema": {
              "type": "integer"
            },
            "example": 789
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action_id"
                ],
                "properties": {
                  "action_id": {
                    "type": "integer",
                    "description": "Action type ID. Must be active for the site.\nUse `GET /actions/types` to discover available action types.\n",
                    "example": 15
                  },
                  "user_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "User (agent) ID who performed the action. When omitted,\nno user is associated with the action.\n",
                    "example": 456
                  },
                  "result": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Action result/comment (free text).",
                    "example": "Client intéressé, rappeler la semaine prochaine"
                  },
                  "date_performed": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "Date when the action was performed. ISO 8601 format.\nWhen omitted, defaults to now.\n",
                    "example": "2026-01-15T14:30:00+01:00"
                  }
                }
              },
              "example": {
                "action_id": 15,
                "user_id": 456,
                "result": "Client intéressé, rappeler la semaine prochaine",
                "date_performed": "2026-01-15T14:30:00+01:00"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Action created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "description": "ID of the created action log entry."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": 98765
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/leads/sellers": {
      "post": {
        "tags": [
          "Leads"
        ],
        "operationId": "createSellerLead",
        "summary": "Create a seller lead",
        "x-badges": [
          {
            "name": "Leads",
            "color": "orange"
          },
          {
            "name": "Websites",
            "color": "blueberry"
          }
        ],
        "description": "Creates a seller lead with a customer and a product.\nRequires `SITE_LEADS` permission and `RS:LEADS_SELLER` right.\n\n**Synchronization & duplicate behavior:**\n- The system automatically checks for duplicate customers by email OR phone OR mobile_phone.\n- Without `check_duplicate` (or `false`): if a duplicate is found, the existing contact\n  is **silently updated** with the provided data and linked to the product.\n  The returned `customer_id` is the existing one.\n- With `check_duplicate: true`: returns HTTP 409 with error code `CONFLICT` when a\n  duplicate is found. You must then decide to proceed without the flag or skip.\n\n**Customer address:** Include address fields directly in the `customer` object\n(`address`, `postalcode`, `city`, `country`, `latitude`, `longitude`).\nThese are persisted on the contact record.\n\n**Product creation:** If `product_id` is not provided, a product is created from\nthe `product` object. Criteria follow the same rules as `POST /products`:\n- `id` accepts either the XML key (e.g. `\"TypeBien\"`) or the numeric ID (e.g. `\"27\"`)\n- `value` format depends on criteria type\n- DPE is auto-calculated from `conso_energ` / `valeur_GES` if letters are not provided\n- GPS is auto-geocoded from `Adresse` + `CodePostal` + `Ville` if `Alentour` is not provided\n\n**User assignment:** If `user_id` is omitted, the system uses the configured\nlead distribution rule (`DEFAULT_LEADS_AFFECTATION`) to automatically assign\na user within the target agency.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSellerLeadRequest"
              },
              "example": {
                "agency_id": 123,
                "user_id": 456,
                "check_duplicate": true,
                "customer": {
                  "firstname": "Marie",
                  "lastname": "Martin",
                  "email": "marie.martin@example.com",
                  "phone": "+33698765432",
                  "gender": "F",
                  "group": 4,
                  "address": "10 rue des Lilas",
                  "postalcode": "69001",
                  "city": "Lyon"
                },
                "product": {
                  "criteria": [
                    {
                      "id": "TypeBien",
                      "value": "Maison"
                    },
                    {
                      "id": "TypeTransaction",
                      "value": "Vente"
                    },
                    {
                      "id": "Surface",
                      "value": "120"
                    },
                    {
                      "id": "NbPieces",
                      "value": "5"
                    },
                    {
                      "id": "CodePostal",
                      "value": "69001"
                    },
                    {
                      "id": "Ville",
                      "value": "Lyon"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Lead created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "customer_id": {
                          "type": "integer",
                          "description": "ID of the created or updated customer."
                        },
                        "product_id": {
                          "type": "integer",
                          "description": "ID of the created or linked product."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": {
                    "customer_id": 789,
                    "product_id": 12345
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid criteria. Returned when a criteria value is not recognized.\nResponse body contains the invalid criteria details.\n"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Duplicate customer detected (only when `check_duplicate: true`).\nThe existing customer was found but not updated.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "CONFLICT",
                    "message": "Duplicate customer found."
                  }
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/leads/buyers": {
      "post": {
        "tags": [
          "Leads"
        ],
        "operationId": "createBuyerLead",
        "summary": "Create a buyer lead",
        "x-badges": [
          {
            "name": "Leads",
            "color": "orange"
          },
          {
            "name": "Websites",
            "color": "blueberry"
          }
        ],
        "description": "Creates a buyer lead with a customer and optional search requests.\nRequires `SITE_LEADS` permission and `RS:LEADS_BUYER` right.\n\n**Synchronization & duplicate behavior:** Same as seller leads (see above).\n\n**Search requests:** Each search request contains criteria with operators:\n- `EGAL` — Equals (exact match)\n- `SUPERIEUR` — Greater than or equal\n- `INFERIEUR` — Less than or equal\n- `CONTIENT` — Contains (for text/multi-value, e.g. zones)\n- `DIFFERENT` — Not equal\n\nUse `GET /criterias/search-requests` to discover available search criteria for your site.\n\n**Customer address:** Include address fields directly in the `customer` object\n(`address`, `postalcode`, `city`, `country`, `latitude`, `longitude`).\n\n**User assignment:** Same auto-assignment logic as seller leads when `user_id` is omitted.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBuyerLeadRequest"
              },
              "example": {
                "agency_id": 123,
                "user_id": 456,
                "check_duplicate": false,
                "customer": {
                  "firstname": "Pierre",
                  "lastname": "Durand",
                  "email": "pierre.durand@example.com",
                  "mobile_phone": "+33611223344",
                  "gender": "M",
                  "group": 1,
                  "address": "5 place Bellecour",
                  "postalcode": "69002",
                  "city": "Lyon"
                },
                "search_request": [
                  {
                    "wording": "Appartement T3 Lyon",
                    "activated": true,
                    "alert_email": true,
                    "criteria": [
                      {
                        "id": "TypeBien",
                        "operator": "EGAL",
                        "value": "Appartement"
                      },
                      {
                        "id": "NbPieces",
                        "operator": "SUPERIEUR",
                        "value": "2"
                      },
                      {
                        "id": "Prix",
                        "operator": "INFERIEUR",
                        "value": "300000"
                      },
                      {
                        "id": "Surface",
                        "operator": "SUPERIEUR",
                        "value": "55"
                      }
                    ]
                  }
                ],
                "comment": "Recherche active, budget flexible"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Lead created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "customer_id": {
                          "type": "integer",
                          "description": "ID of the created or updated customer."
                        },
                        "search_request_id": {
                          "type": "array",
                          "items": {
                            "type": "integer"
                          },
                          "description": "IDs of the created search requests."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": {
                    "customer_id": 790,
                    "search_request_id": [
                      42
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid criteria in search request. Returned when a criteria value\nis not recognized. Response body contains the invalid criteria details.\n"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Duplicate customer detected (only when `check_duplicate: true`).\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "CONFLICT",
                    "message": "Duplicate customer found."
                  }
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/transactions/agreement/search": {
      "post": {
        "tags": [
          "Transactions"
        ],
        "operationId": "searchSaleAgreements",
        "summary": "Search sale agreements",
        "x-badges": [
          {
            "name": "Sales",
            "color": "red"
          }
        ],
        "description": "Searches sale agreements (compromis de vente) with cursor-based pagination.\nRequires `SITE_PRODUCT:READ` permission and `RS:COMPROMIS_SEARCH` right.\n\n**Use cases:**\n- Retrieve all sales completed after a given date\n- Find a sale agreement by product reference or customer name\n- Export sales data for valuation reports (avis de valeur)\n\n**Key response fields for sale tracking:**\n- `buyer_price` — actual buyer price (price including agency fees)\n- `product_price` — property net price\n- `date_act` — deed signing date (= effective sale date)\n- `date_agreement` — compromise signature date\n\n**Includes:** Use the `include` field to load additional relations (buyer, seller, fees, agencies, etc.).\nAvailable includes: `compromis_cs`, `agencies`, `buyer`, `buyer_groups`, `buyer_address`,\n`buyer_origin`, `buyer_admin`, `buyer_gender`, `buyer_informations`, `buyer_extended`,\n`buyer_relations`, `seller`, `seller_groups`, `seller_address`, `seller_origin`,\n`seller_admin`, `followed_nego`, `recommandation`, `fees`, `fees_nego`, `analytic`\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchTransactionRequest"
              },
              "example": {
                "product_model": "REF-2025-001",
                "last_modified": "2025-01-01T00:00:00+01:00",
                "per_page": 50,
                "include": "buyer,seller,fees"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Paginated list of sale agreements",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SaleTransactionResponse"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/CursorPaginationMeta-3"
                    },
                    "links": {
                      "$ref": "#/components/schemas/CursorPaginationLinks-3"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/transactions/sales-offers/search": {
      "post": {
        "tags": [
          "Transactions"
        ],
        "operationId": "searchSalesOffers",
        "summary": "Search sales offers",
        "x-badges": [
          {
            "name": "Sales",
            "color": "red"
          }
        ],
        "description": "Searches sales offers (offres d'achat) with cursor-based pagination.\nRequires `SITE_PRODUCT:READ` permission and `RS:OFFER_SEARCH` right.\n\nOffers have an additional `offer_status` field indicating the current state\nof the offer (`attente`, `acceptee`, `refusee`, `annulee`).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchTransactionRequest"
              },
              "example": {
                "offer_status": "attente",
                "agency_ids": [
                  123,
                  456
                ],
                "per_page": 25
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Paginated list of sales offers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SaleTransactionResponse"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/CursorPaginationMeta-3"
                    },
                    "links": {
                      "$ref": "#/components/schemas/CursorPaginationLinks-3"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/transactions/leases/search": {
      "post": {
        "tags": [
          "Transactions"
        ],
        "operationId": "searchLeases",
        "summary": "Search leases",
        "x-badges": [
          {
            "name": "Sales",
            "color": "red"
          }
        ],
        "description": "Searches lease agreements (baux de location) with cursor-based pagination.\nRequires `SITE_PRODUCT:READ` permission and `RS:LEASE_SEARCH` right.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchTransactionRequest"
              },
              "example": {
                "agency_ids": [
                  123
                ],
                "last_created": "2025-06-01T00:00:00+02:00",
                "per_page": 25
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Paginated list of leases",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SaleTransactionResponse"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/CursorPaginationMeta-3"
                    },
                    "links": {
                      "$ref": "#/components/schemas/CursorPaginationLinks-3"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/agreement/{compromisId}": {
      "get": {
        "tags": [
          "Transactions"
        ],
        "operationId": "getSaleTransaction",
        "summary": "Get a sale transaction",
        "x-badges": [
          {
            "name": "Sales",
            "color": "red"
          }
        ],
        "description": "Returns detailed information about a sale transaction (compromis, offer, or lease).\nRequires `SITE_PRODUCT:READ` permission and `RS:COMPROMIS_GET` right.\n\nUse `?include=` to load additional relations. Available includes:\n`compromis_cs`, `agencies`, `buyer`, `buyer_groups`, `buyer_address`,\n`buyer_origin`, `buyer_admin`, `buyer_gender`, `buyer_informations`, `buyer_extended`,\n`buyer_relations`, `seller`, `seller_groups`, `seller_address`, `seller_origin`,\n`seller_admin`, `followed_nego`, `recommandation`, `fees`, `fees_nego`, `analytic`\n",
        "parameters": [
          {
            "name": "compromisId",
            "in": "path",
            "required": true,
            "description": "Unique identifier of the sale transaction (compromis).",
            "schema": {
              "type": "integer"
            },
            "example": 5678
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of relations to include in the response.\nEach include triggers additional eager loading on the server side.\n",
            "schema": {
              "type": "string"
            },
            "example": "buyer,seller,fees,agencies"
          }
        ],
        "responses": {
          "200": {
            "description": "Sale transaction details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SaleTransactionResponse"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": 5678,
                    "status": {
                      "id": 3,
                      "label": "Actée"
                    },
                    "offer_status": "acceptee",
                    "number": "COMP-2025-001",
                    "transaction_type": "Vente",
                    "product_id": 12345,
                    "customer_id": 789,
                    "seller_id": 790,
                    "created_at": "2025-03-01T10:00:00+01:00",
                    "updated_at": "2025-06-01T14:30:00+02:00",
                    "date_agreement": "2025-03-15T00:00:00+01:00",
                    "date_act": "2025-06-01T00:00:00+02:00",
                    "date_cancellation": null,
                    "buyer_price": 355000,
                    "product_price": 350000
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "tags": [
          "Transactions"
        ],
        "operationId": "updateSaleTransaction",
        "summary": "Update a sale transaction",
        "x-badges": [
          {
            "name": "Sales",
            "color": "red"
          }
        ],
        "description": "Updates billing and invoicing fields on a sale transaction.\nRequires `SITE_PRODUCT:WRITE` permission and `RS:COMPROMIS_UPDATE` right.\n\n**Important limitations:**\n- This endpoint only accepts **billing/invoicing fields** (conformity, payment, invoice numbers, fees).\n- It does **NOT** allow updating: `buyer_price`, `product_price`, `date_act`, `date_agreement`, `status`.\n- Sale price and deed date are set when the agreement is created in the back-office.\n- There is no `POST /agreement` endpoint in the Site API context — sale agreements\n  are created via the internal user interface or the User API.\n\n**Fees:** Each fee entry must reference an existing `id` from the transaction's\nfee records and provide a `percentage` between 0 and 100.\n",
        "parameters": [
          {
            "name": "compromisId",
            "in": "path",
            "required": true,
            "description": "Unique identifier of the sale transaction to update.",
            "schema": {
              "type": "integer"
            },
            "example": 5678
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTransactionRequest"
              },
              "example": {
                "conformity": 1,
                "billing_status": 2,
                "payment_amount": 15000,
                "payment_date": "2025-06-01T00:00:00+02:00",
                "num_bill_buyer": "FA-2025-001",
                "date_bill_buyer": "2025-06-05",
                "fees": [
                  {
                    "id": 1,
                    "percentage": 5
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Updated successfully"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/users": {
      "get": {
        "tags": [
          "Users"
        ],
        "operationId": "listUsers",
        "summary": "List users",
        "x-badges": [
          {
            "name": "Websites",
            "color": "blueberry"
          }
        ],
        "description": "Returns all users (agents/collaborators) for the authenticated site,\nwith optional filters and cursor-based pagination.\nRequires `SITE_ADMIN:READ` permission and `RS:ADMIN_ALL` right.\n\n**Filters:** All query parameters are optional. Combine them to narrow results.\n\n**Pagination:** Uses cursor-based pagination. Pass `meta.next_cursor` from the\nprevious response as the `cursor` parameter to fetch the next page.\n",
        "parameters": [
          {
            "name": "firstname",
            "in": "query",
            "required": false,
            "description": "Filter by first name (partial match).",
            "schema": {
              "type": "string"
            },
            "example": "Jean"
          },
          {
            "name": "lastname",
            "in": "query",
            "required": false,
            "description": "Filter by last name (partial match).",
            "schema": {
              "type": "string"
            },
            "example": "Dupont"
          },
          {
            "name": "email",
            "in": "query",
            "required": false,
            "description": "Filter by email (partial match).",
            "schema": {
              "type": "string"
            },
            "example": "dupont@example.com"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by user status:\n- `1` — Active\n- `0` — Archived\n- `3` — Hidden/deleted\n",
            "schema": {
              "type": "integer"
            },
            "example": 1
          },
          {
            "name": "without_deleted",
            "in": "query",
            "required": false,
            "description": "When true, excludes deleted/archived users from results.\nEquivalent to filtering out status 0 and 3.\n",
            "schema": {
              "type": "boolean"
            },
            "example": true
          },
          {
            "name": "delegation",
            "in": "query",
            "required": false,
            "description": "Filter by delegation flag (users who can delegate their work).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "agency_ids",
            "in": "query",
            "required": false,
            "description": "Filter by agency IDs. Only returns users attached to these agencies.\nEach ID must be within the authenticated site's scope.\n",
            "schema": {
              "type": "array",
              "items": {
                "type": "integer"
              }
            },
            "example": [
              123,
              456
            ]
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Number of results per page. Maximum 200.\n",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            },
            "example": 50
          },
          {
            "name": "telephone_code",
            "in": "query",
            "required": false,
            "description": "When true, phone numbers are formatted in international format\n(e.g. `+33601020304` instead of `0601020304`).\n",
            "schema": {
              "type": "boolean"
            },
            "example": true
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of users",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UserResponse"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/CursorPaginationMeta-2"
                    },
                    "links": {
                      "$ref": "#/components/schemas/CursorPaginationLinks-2"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Users"
        ],
        "operationId": "createUser",
        "summary": "Create a user",
        "x-badges": [
          {
            "name": "Access"
          }
        ],
        "description": "Creates a new user (agent/collaborator) in the system.\nRequires `SITE_ADMIN:WRITE` permission and `RS:ADMIN_CREATE` right.\n\n**Password requirements (RGPD):**\n- Length: 12-40 characters\n- Must contain: uppercase, lowercase, digit, special character\n\n**Agency assignment:** The `agency_id` is the user's primary agency.\nUse `agencies_list` to grant access to additional agencies.\n\n**User types:** Use `GET /users/types` to discover available types\n(e.g. Négociateur, Gestionnaire, etc.).\n\n**Groups:** Use `GET /users/groups` to discover available groups\n(determines access level and permissions within the agency).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUserRequest"
              },
              "example": {
                "firstname": "Marie",
                "lastname": "Martin",
                "email": "marie.martin@agence.fr",
                "password": "S3cur3P@ss!2026",
                "status": 1,
                "agency_id": 123,
                "group_id": 2,
                "gender": "f",
                "phone": "+33498765432",
                "mobile_phone": "+33698765432",
                "user_types": [
                  1
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "User created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "description": "ID of the created user."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": 582603
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/users/{userId}": {
      "get": {
        "tags": [
          "Users"
        ],
        "operationId": "getUser",
        "summary": "Get a user",
        "x-badges": [
          {
            "name": "Properties",
            "color": "green"
          },
          {
            "name": "Customers",
            "color": "purple"
          },
          {
            "name": "Websites",
            "color": "blueberry"
          }
        ],
        "description": "Returns detailed information about a user (agent/collaborator).\nRequires `SITE_ADMIN:READ` permission and `RS:ADMIN_GET` right.\n\nUse `?include=logs` to include the user's recent action log entries.\n",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "Unique identifier of the user.",
            "schema": {
              "type": "integer"
            },
            "example": 582602
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Additional data to include. Currently available: `logs`\n(recent action log entries for this user).\n",
            "schema": {
              "type": "string"
            },
            "example": "logs"
          }
        ],
        "responses": {
          "200": {
            "description": "User details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UserResponse"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Users"
        ],
        "operationId": "updateUser",
        "summary": "Update a user",
        "x-badges": [
          {
            "name": "Access"
          }
        ],
        "description": "Partially updates a user. Only provided fields are modified.\nRequires `SITE_ADMIN:WRITE` permission and `RS:ADMIN_UPDATE` right.\n\n**Note:** Password can be changed via this endpoint. Same RGPD requirements apply.\n",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "Unique identifier of the user to update.",
            "schema": {
              "type": "integer"
            },
            "example": 582602
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateUserRequest"
              },
              "example": {
                "phone": "+33498765000",
                "status": 1,
                "delegation": true
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Updated successfully"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      },
      "delete": {
        "tags": [
          "Users"
        ],
        "operationId": "deleteUser",
        "summary": "Delete a user",
        "x-badges": [
          {
            "name": "Access"
          }
        ],
        "description": "Deletes a user. Their properties, customers, and other entities are\nautomatically reassigned according to the site's configuration rules.\nRequires `SITE_ADMIN:WRITE` permission and `RS:ADMIN_DELETE` right.\n\n**Warning:** This operation is irreversible. The user will be marked as deleted\n(status 3) and removed from active listings.\n",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "Unique identifier of the user to delete.",
            "schema": {
              "type": "integer"
            },
            "example": 582602
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted successfully"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/users/groups": {
      "get": {
        "tags": [
          "Users"
        ],
        "operationId": "listUserGroups",
        "summary": "List user groups",
        "x-badges": [
          {
            "name": "Access"
          }
        ],
        "description": "Returns all available user groups (permission levels) for the site.\nRequires `SITE_CONFIG` permission and `RS:ADMIN_GROUP_LIST` right.\n\nGroups determine the access level and permissions of a user within the agency\n(e.g. \"Administrateur\", \"Négociateur\", \"Assistante\").\nUse group IDs when creating or updating users.\n",
        "responses": {
          "200": {
            "description": "List of groups",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UserGroupResponse"
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": 1,
                      "name": "Administrateur",
                      "delegation": true
                    },
                    {
                      "id": 2,
                      "name": "Négociateur",
                      "delegation": false
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/users/types": {
      "get": {
        "tags": [
          "Users"
        ],
        "operationId": "listUserTypes",
        "summary": "List user types",
        "x-badges": [
          {
            "name": "Access"
          }
        ],
        "description": "Returns all available user types (professional roles) for the site.\nRequires `SITE_CONFIG` permission and `RS:ADMIN_TYPE_LIST` right.\n\nUser types categorize the professional role (e.g. \"Négociateur\", \"Gestionnaire\",\n\"Directeur\"). A user can have multiple types.\nUse type IDs when creating or updating users.\n",
        "responses": {
          "200": {
            "description": "List of types",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UserTypeResponse"
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": 1,
                      "name": "Négociateur",
                      "name_female": "Négociatrice"
                    },
                    {
                      "id": 2,
                      "name": "Directeur",
                      "name_female": "Directrice"
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/users/genders": {
      "get": {
        "tags": [
          "Users"
        ],
        "operationId": "listGenders",
        "summary": "List genders",
        "x-badges": [
          {
            "name": "Access"
          }
        ],
        "description": "Returns all available gender options.\nRequires `SITE_CONFIG` permission and `RS:ADMIN_GENDERS` right.\n\nGender codes are used when creating users or customers (e.g. `\"m\"`, `\"f\"`).\nSome genders represent legal entities (companies) — check the `is_entity` flag.\n",
        "responses": {
          "200": {
            "description": "List of genders",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/GenderResponse"
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "m",
                      "description": "Monsieur",
                      "abbreviation": "M.",
                      "is_entity": false,
                      "private": false
                    },
                    {
                      "id": "f",
                      "description": "Madame",
                      "abbreviation": "Mme",
                      "is_entity": false,
                      "private": false
                    },
                    {
                      "id": "s",
                      "description": "Société",
                      "abbreviation": "Sté",
                      "is_entity": true,
                      "private": false
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/hooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhooks",
        "summary": "List webhooks",
        "description": "Returns all webhooks registered by the authenticated client.\n\nEach webhook has a list of subscribed event types (`origines`) and a target URL.\n",
        "responses": {
          "200": {
            "description": "List of webhooks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookResponse"
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": 42,
                      "url": "https://portail.example.com/webhook",
                      "headers": {
                        "X-Api-Key": "secret"
                      },
                      "authId": null,
                      "format": null,
                      "origines": [
                        "CUSTOMER_CREATE",
                        "PRODUCT_UPDATE"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "createWebhook",
        "summary": "Create a webhook",
        "description": "Registers a new webhook subscription for specific events.\n\n## How webhooks work\n\nWebhooks allow your application to receive real-time notifications when events\noccur in Immofacile (e.g. a customer is created, a product is updated).\n\n### Subscription\n1. Register a webhook by providing a target URL and the event types you want to subscribe to.\n2. When a subscribed event occurs, Immofacile sends an HTTP `POST` request to your URL\n   with the event payload.\n\n### Payload format\nEach webhook payload contains:\n- `event_type` — the event that triggered the notification\n- `resource_id` — the ID of the affected resource\n- `timestamp` — when the event occurred\n\n### Error handling and retries\n- If your endpoint returns a **422**, the resource is marked as errored with the\n  reason from your response body (useful for rejecting invalid data).\n- If your endpoint returns any other error (4xx/5xx or timeout), Immofacile retries\n  up to **3 times** with exponentially increasing delay.\n- After 3 failed retries, the webhook is **suspended** and an internal log is created.\n- A suspended webhook stops receiving events until it is manually reactivated\n  (via `PUT /hooks/{id}`).\n\n### Best practices\n- Respond with HTTP 200 within **10 seconds** to acknowledge receipt.\n- Process the payload asynchronously on your side if business logic is heavy.\n- Return 422 with a descriptive body only when the data is intentionally rejected\n  (e.g. duplicate detection, invalid state for your system).\n- Use the `headers` field to include an API key for authenticating incoming requests\n  on your server.\n\n### Available event types\nEvent types follow the pattern `RESOURCE_ACTION`. Common examples:\n- `CUSTOMER_CREATE` — New customer created\n- `CUSTOMER_UPDATE` — Customer modified\n- `PRODUCT_CREATE` — New product created\n- `PRODUCT_UPDATE` — Product modified\n- `PRODUCT_DELETE` — Product deleted\n\nThe full list of available events is validated server-side. Invalid event types\nwill be rejected with a 422.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              },
              "example": {
                "url": "https://portail.example.com/webhook",
                "origines": [
                  "CUSTOMER_CREATE",
                  "CUSTOMER_UPDATE",
                  "PRODUCT_CREATE",
                  "PRODUCT_UPDATE"
                ],
                "headers": {
                  "X-Api-Key": "my-secret-key"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "description": "ID of the created webhook."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": 42
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/hooks/{hookId}": {
      "put": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "updateWebhook",
        "summary": "Update a webhook",
        "description": "Updates an existing webhook (URL, events, headers, auth).\n\nUse this endpoint to:\n- Change the target URL\n- Add/remove subscribed event types\n- Update authentication headers\n- Reactivate a suspended webhook (update it with the corrected URL)\n",
        "parameters": [
          {
            "name": "hookId",
            "in": "path",
            "required": true,
            "description": "ID of the webhook to update.",
            "schema": {
              "type": "integer"
            },
            "example": 42
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhookRequest"
              },
              "example": {
                "url": "https://new-portail.example.com/webhook",
                "origines": [
                  "CUSTOMER_CREATE",
                  "PRODUCT_UPDATE",
                  "PRODUCT_DELETE"
                ],
                "headers": {
                  "X-Api-Key": "new-secret"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookResponse"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "deleteWebhook",
        "summary": "Delete a webhook",
        "description": "Removes a webhook subscription. Events will no longer be delivered to this URL.\nThis action is **irreversible** — to temporarily stop deliveries without losing\nthe configuration, consider updating the URL to a placeholder instead.\n",
        "parameters": [
          {
            "name": "hookId",
            "in": "path",
            "required": true,
            "description": "ID of the webhook to delete.",
            "schema": {
              "type": "integer"
            },
            "example": 42
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted successfully"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/configs/listes/{listeId}/values": {
      "get": {
        "tags": [
          "Configs"
        ],
        "operationId": "getListValues",
        "summary": "Get list values",
        "x-badges": [
          {
            "name": "Properties",
            "color": "green"
          }
        ],
        "description": "Returns all values for a given referential list (liste_id).\nLists contain predefined values used in forms and validated fields.\n\n**Common list IDs:**\n| ID | Usage |\n|----|-------|\n| 1 | Room types (Chambre, Cuisine, Salon...) — used in product rooms `type` |\n| 2 | Expositions (Nord, Sud, Est, Ouest...) — used in rooms `exposition` |\n| 3 | Views (Jardin, Mer, Rue...) — used in rooms `vue` |\n| 4 | Floor types (Parquet, Carrelage...) — used in rooms `sol` |\n| 5 | Customer types |\n| 7 | Compromis statuses |\n| 8 | Forme juridique (legal form) |\n| 10 | Situation maritale (marital status) |\n| 11 | Type de contrat (contract type) |\n| 13 | Motifs d'annulation (cancellation reasons) |\n| 35 | Motif d'achat (purchase motivation) |\n\nUse this endpoint to discover valid values before sending data that references\nthese lists (e.g. room types, customer legal form, etc.).\n",
        "parameters": [
          {
            "name": "listeId",
            "in": "path",
            "required": true,
            "description": "The list identifier. Can be a single ID (e.g. `\"1\"`) or comma-separated\nIDs to fetch multiple lists in one call (e.g. `\"1,2,3\"`).\n",
            "schema": {
              "type": "string"
            },
            "example": "1"
          }
        ],
        "responses": {
          "200": {
            "description": "List values",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Liste"
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": 1,
                      "values": [
                        {
                          "id": 1,
                          "name": "Chambre",
                          "order": 1
                        },
                        {
                          "id": 2,
                          "name": "Cuisine",
                          "order": 2
                        },
                        {
                          "id": 3,
                          "name": "Salon",
                          "order": 3
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden — missing required pack or right"
          }
        }
      }
    },
    "/calendar/events/types": {
      "get": {
        "tags": [
          "Agenda"
        ],
        "operationId": "listEventTypes",
        "summary": "List event types",
        "description": "Returns the agency configured event types with their display form\n(visible and collapsed fields). Cursor-based pagination.\nRequires the `MODULE_IMMO_AGENDA` module and `SITE_CALENDAR:READ` permission.\n",
        "parameters": [
          {
            "name": "perPage",
            "in": "query",
            "description": "Maximum number of event types to return per page.\nValues above 50 are capped to 50.\n",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque pagination cursor. Pass the `meta.next_cursor` value returned\nby the previous response to fetch the next page. Omit for the first page.\n",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of event types",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EventTypeResponse"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/CursorPaginationMeta-4"
                    },
                    "links": {
                      "$ref": "#/components/schemas/CursorPaginationLinks-3"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/calendar/permanences/timeframe": {
      "get": {
        "tags": [
          "Agenda"
        ],
        "operationId": "getPermanenceTimeframe",
        "summary": "Get permanence timeframe",
        "description": "Returns the client reception (permanence) slots configured for an agency,\ngrouped by day of week.\nRequires `SITE_CALENDAR:READ`.\n",
        "parameters": [
          {
            "name": "agency_id",
            "in": "query",
            "required": true,
            "description": "Target agency ID whose permanence configuration is requested.\nMust belong to the agencies the authenticated client can read,\notherwise a 403 is returned.\n",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Permanence timeframe grouped by day",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PermanenceTimeframeResponse"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/calendar/permanences/timeframe/users": {
      "get": {
        "tags": [
          "Agenda"
        ],
        "operationId": "listPermanenceUsers",
        "summary": "List permanence users",
        "description": "Returns the users assigned to permanence slots over a period for an agency.\nDefaults to the current week when `from`/`to` are omitted.\nRequires `SITE_CALENDAR:READ`.\n",
        "parameters": [
          {
            "name": "agency_id",
            "in": "query",
            "required": true,
            "description": "Target agency ID. Must be among the agencies the authenticated client can read,\notherwise a 403 is returned.\n",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "tz",
            "in": "query",
            "description": "IANA timezone used to format the `start`/`end` datetimes in the response.\nDefaults to UTC when omitted.\n",
            "schema": {
              "type": "string",
              "example": "Europe/Paris"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Start of the period (local datetime, no timezone suffix, format YYYY-MM-DDTHH:MM:SS).\nDefaults to the start of the current week (Monday 00:00 UTC) when omitted.\n",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$",
              "example": "2026-01-01T00:00:00"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "End of the period (local datetime, no timezone suffix, format YYYY-MM-DDTHH:MM:SS).\nDefaults to the end of the current week (Sunday 23:59 UTC) when omitted.\n",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$",
              "example": "2026-01-07T23:59:59"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of permanence users",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PermanenceUserResponse"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/calendar/users/{userId}/working-time": {
      "get": {
        "tags": [
          "Agenda"
        ],
        "operationId": "getUserWorkingTime",
        "summary": "Get user working time",
        "description": "Returns the working time slots of a user for the current week, grouped by day.\nRequires `SITE_CALENDAR:READ`.\n",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "ID of the user (agent/collaborator) whose working time is requested.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "tz",
            "in": "query",
            "description": "IANA timezone used to express the slot hours/minutes.\nDefaults to UTC when omitted.\n",
            "schema": {
              "type": "string",
              "example": "Europe/Paris"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Working time grouped by day",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkingTimeResponse"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/calendar/users/{userId}/availability": {
      "get": {
        "tags": [
          "Agenda"
        ],
        "operationId": "getUserAvailability",
        "summary": "Get user availability",
        "description": "Returns the available slots of a user over a period, computed from their\nworking time and existing events. Defaults to the current week when\n`from`/`to` are omitted.\nRequires `SITE_CALENDAR:READ`.\n",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "ID of the user (agent/collaborator) whose availability is computed.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "tz",
            "in": "query",
            "description": "IANA timezone used to format the `start`/`end` datetimes of the returned slots.\nDefaults to UTC when omitted.\n",
            "schema": {
              "type": "string",
              "example": "Europe/Paris"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Start of the period (local datetime, format YYYY-MM-DDTHH:MM:SS).\nDefaults to the start of the current week (Monday 00:00 UTC) when omitted.\n",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$",
              "example": "2026-01-01T00:00:00"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "End of the period (local datetime, format YYYY-MM-DDTHH:MM:SS).\nDefaults to the end of the current week (Sunday 23:59 UTC) when omitted.\n",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$",
              "example": "2026-01-07T23:59:59"
            }
          },
          {
            "name": "slot_duration",
            "in": "query",
            "description": "Desired duration of each returned availability slot, in minutes.\nWhen omitted, a default slot granularity is used.\n",
            "schema": {
              "type": "integer",
              "example": 30
            }
          },
          {
            "name": "exclude_types",
            "in": "query",
            "description": "Event type keys to ignore when computing availability. Events of these\ntypes do not make the corresponding slots unavailable.\n",
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "example": [
                "FREE"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of available slots",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AvailabilitySlot"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/calendar/users/{userId}/shared-agenda": {
      "get": {
        "tags": [
          "Agenda"
        ],
        "operationId": "listSharedAgendas",
        "summary": "List shared agendas",
        "description": "Returns the agendas accessible by the given user (users whose calendar is shared with them).\nRequires `SITE_CALENDAR:READ`.\n",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "description": "ID of the user for whom accessible (shared) agendas are listed.\nThe response contains the users whose calendar this user is allowed to view.\n",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of accessible agendas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SharedAgendaResponse"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/calendar/events": {
      "get": {
        "tags": [
          "Agenda"
        ],
        "operationId": "searchEvents",
        "summary": "Search events",
        "description": "Searches events over a period with optional filters. Defaults to the current\nday when `from`/`to` are omitted. Recurring events are expanded by default.\nRequires `SITE_CALENDAR:READ`.\n",
        "parameters": [
          {
            "name": "user_id",
            "in": "query",
            "description": "Restricts event visibility to this user's perspective. Affects which\nconfidential/private events are returned.\n",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "user_ids",
            "in": "query",
            "description": "Restricts the result to events involving these user (agent) IDs as participants.\n",
            "schema": {
              "type": "array",
              "items": {
                "type": "integer"
              }
            }
          },
          {
            "name": "agency_ids",
            "in": "query",
            "description": "Restricts the result to events belonging to these agency IDs.\nEach ID must be among the agencies the authenticated client can read,\notherwise a 403 is returned.\n",
            "schema": {
              "type": "array",
              "items": {
                "type": "integer"
              }
            }
          },
          {
            "name": "tz",
            "in": "query",
            "description": "IANA timezone applied to the returned event datetimes.\nDefaults to UTC when omitted.\n",
            "schema": {
              "type": "string",
              "example": "Europe/Paris"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Start of the search window (local datetime, format YYYY-MM-DDTHH:MM:SS).\nDefaults to the start of the current day (00:00 UTC) when omitted.\n",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$",
              "example": "2026-01-01T00:00:00"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "End of the search window (local datetime, format YYYY-MM-DDTHH:MM:SS).\nDefaults to the end of the current day (23:59 UTC) when omitted.\n",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$",
              "example": "2026-01-31T23:59:59"
            }
          },
          {
            "name": "perPage",
            "in": "query",
            "description": "Maximum number of events to return. Values above 50 are capped to 50.\n",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Number of events to skip from the start of the result set (offset pagination).",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "name": "excluded_types",
            "in": "query",
            "description": "Event type ids to exclude from the result (e.g. to hide working-time or\nfree slots from a calendar view).\n",
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "example": [
                "1",
                "2"
              ]
            }
          },
          {
            "name": "recurring",
            "in": "query",
            "description": "When true (default), recurring events are expanded into their individual\noccurrences within the search window. When false, only the master event is returned.\n",
            "schema": {
              "type": "boolean",
              "default": true
            }
          },
          {
            "name": "archived",
            "in": "query",
            "description": "When false (default), archived events are excluded. Set to true to include\narchived events in the result.\n",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of events",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EventResponse"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      },
      "post": {
        "tags": [
          "Agenda"
        ],
        "operationId": "createEvent",
        "summary": "Create an event",
        "description": "Creates a calendar event. Exactly one participant must be flagged as\n`is_organizer` and exactly one as `is_creator`, and each participant\nmust have a unique `user_id`.\nRequires `SITE_CALENDAR:WRITE`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateEventRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/EventResponse"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/calendar/events/{eventId}": {
      "get": {
        "tags": [
          "Agenda"
        ],
        "operationId": "getEvent",
        "summary": "Get an event",
        "description": "Returns a single event by ID.\nRequires `SITE_CALENDAR:READ`.\n",
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "description": "ID of the event to retrieve.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Event details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/EventResponse"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Agenda"
        ],
        "operationId": "updateEvent",
        "summary": "Update an event",
        "description": "Partially updates an event. When participants are provided, exactly one\nmust be flagged `is_organizer` and exactly one `is_creator`, with unique `user_id`.\nRequires `SITE_CALENDAR:WRITE`.\n",
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "description": "ID of the event to update.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateEventRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/EventResponse"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      },
      "delete": {
        "tags": [
          "Agenda"
        ],
        "operationId": "deleteEvent",
        "summary": "Delete an event",
        "description": "Permanently deletes an event.\nRequires `SITE_CALENDAR:WRITE`.\n",
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "description": "ID of the event to permanently delete. This is irreversible; to keep the\nevent but hide it, use the status endpoint to archive it instead.\n",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted successfully"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/calendar/events/{eventId}/status": {
      "patch": {
        "tags": [
          "Agenda"
        ],
        "operationId": "changeEventStatus",
        "summary": "Change event status",
        "description": "Archives or restores an event.\nRequires `SITE_CALENDAR:WRITE`.\n",
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "description": "ID of the event to archive or restore.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "archive"
                ],
                "properties": {
                  "archive": {
                    "type": "boolean",
                    "description": "Set to true to archive the event (hidden from default listings but\nkept and restorable), or false to restore a previously archived event.\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event status changed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/EventResponse"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    }
  },
  "webhooks": {
    "customerEvent": {
      "post": {
        "operationId": "onCustomerEvent",
        "summary": "Customer event",
        "tags": [
          "Webhooks"
        ],
        "description": "Sent to your registered URL when a subscribed **customer** event occurs\n(`CUSTOMER_CREATE`, `CUSTOMER_UPDATE`).\n\nRespond with HTTP `200` within 10 seconds to acknowledge receipt.\nReturn `422` to intentionally reject the resource; any other error\ntriggers up to 3 retries before the webhook is suspended.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEventPayload"
              },
              "example": {
                "event_type": "CUSTOMER_CREATE",
                "resource_id": 789,
                "timestamp": "2026-09-11T14:30:00+02:00"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event acknowledged."
          },
          "422": {
            "description": "Resource intentionally rejected by the receiver."
          }
        }
      }
    },
    "productEvent": {
      "post": {
        "operationId": "onProductEvent",
        "summary": "Product event",
        "tags": [
          "Webhooks"
        ],
        "description": "Sent to your registered URL when a subscribed **product** event occurs\n(`PRODUCT_CREATE`, `PRODUCT_UPDATE`, `PRODUCT_DELETE`).\n\nRespond with HTTP `200` within 10 seconds to acknowledge receipt.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEventPayload"
              },
              "example": {
                "event_type": "PRODUCT_UPDATE",
                "resource_id": 12345,
                "timestamp": "2026-09-11T14:30:00+02:00"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event acknowledged."
          },
          "422": {
            "description": "Resource intentionally rejected by the receiver."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "opaque"
      },
      "basicAuth": {
        "type": "http",
        "scheme": "basic",
        "description": "HTTP Basic Auth with client_id as username and client_secret as password (base64 encoded)"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "$ref": "#/components/schemas/Error"
          }
        }
      },
      "ValidationErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "VALIDATION_ERROR"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "DiscoveryAgency": {
        "type": "object",
        "description": "An agency (manufacturer) accessible by the authenticated client.",
        "properties": {
          "site_id": {
            "type": "integer",
            "description": "Site ID this agency belongs to.",
            "example": 100
          },
          "agency_id": {
            "type": "integer",
            "description": "Agency ID. Use this value as `agency_id` in create/update requests.\n",
            "example": 123
          },
          "name": {
            "type": "string",
            "description": "Commercial name of the agency.",
            "example": "Agence Centre Ville"
          },
          "legal_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Legal company name.",
            "example": "SAS Immobilier Centre"
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "description": "Street address.",
            "example": "15 rue de la République"
          },
          "postal_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Postal code.",
            "example": "69001"
          },
          "city": {
            "type": [
              "string",
              "null"
            ],
            "description": "City.",
            "example": "Lyon"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Agency contact email.",
            "example": "centre@agence.fr"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Agency phone number.",
            "example": "+33472123456"
          },
          "siret": {
            "type": [
              "string",
              "null"
            ],
            "description": "SIRET number (French business ID, 14 digits).",
            "example": "12345678901234"
          },
          "siren": {
            "type": [
              "string",
              "null"
            ],
            "description": "SIREN number (French company ID, 9 digits).",
            "example": "123456789"
          }
        }
      },
      "AgencyResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "model": {
            "type": [
              "string",
              "null"
            ]
          },
          "schedule": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_demo": {
            "type": "boolean"
          },
          "fee_schedule": {
            "type": [
              "string",
              "null"
            ],
            "description": "Fee schedule image URL"
          },
          "site_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "site_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "logo": {
            "type": [
              "string",
              "null"
            ],
            "description": "Logo URL"
          },
          "picture": {
            "type": [
              "string",
              "null"
            ],
            "description": "Agency photo URL"
          },
          "location": {
            "type": "object",
            "properties": {
              "address": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "zip_code": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "city": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "coordinates": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "latitude": {
                    "type": "number"
                  },
                  "longitude": {
                    "type": "number"
                  }
                }
              }
            }
          },
          "phones": {
            "type": "object",
            "properties": {
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone_location": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "emails": {
            "type": "object",
            "properties": {
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email_leasing": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "details": {
            "type": [
              "object",
              "null"
            ],
            "description": "Agency detail information"
          }
        }
      },
      "CursorPaginationMeta": {
        "type": "object",
        "properties": {
          "per_page": {
            "type": "integer",
            "example": 50
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "example": "eyJpZCI6MTIzfQ=="
          },
          "prev_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "CursorPaginationLinks": {
        "type": "object",
        "properties": {
          "next": {
            "type": [
              "string",
              "null"
            ]
          },
          "prev": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "AgencyFullResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AgencyResponse"
          },
          {
            "type": "object",
            "properties": {
              "social_links": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "Social media links"
              },
              "users": {
                "type": [
                  "array",
                  "null"
                ],
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "firstname": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "lastname": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "email": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "phone": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "mobile_phone": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "status": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "picture": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                },
                "description": "List of users (agents) attached to the agency"
              }
            }
          }
        ]
      },
      "CreateProductRequest": {
        "type": "object",
        "required": [
          "agency_id",
          "criteria"
        ],
        "description": "Request body for creating a product. The `agency_id` and `criteria` fields are required.\nAll other fields are optional.\n",
        "properties": {
          "agency_id": {
            "type": "integer",
            "description": "Agency ID for the product. Must be within the authenticated site's scope\n(validated by `manufacturer_in_scope`). Use `GET /agencies` to list available IDs.\n",
            "example": 123
          },
          "user_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Assigned user (agent) ID. The agent responsible for this property listing.\n",
            "example": 456
          },
          "model": {
            "type": [
              "string",
              "null"
            ],
            "description": "Product reference (mandate number, agency reference, your own identifier).\n**Auto-generated** if omitted — the system produces the next sequential reference\nfor the agency. You can use this field to store your own cross-reference ID.\n",
            "example": "REF-2025-001"
          },
          "descriptions": {
            "type": [
              "array",
              "null"
            ],
            "description": "Web descriptions for the product (multilingual). Each entry requires a\n`language_code` (ISO 639-2, e.g. `\"fr\"`).\n",
            "items": {
              "type": "object",
              "required": [
                "language_code"
              ],
              "properties": {
                "title": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Short title.",
                  "example": "Bel appartement lumineux"
                },
                "description": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Full description text.",
                  "example": "Situé au 3ème étage avec ascenseur, cet appartement..."
                },
                "language_code": {
                  "type": "string",
                  "description": "ISO 639-2 language code.",
                  "example": "fr"
                }
              }
            }
          },
          "criteria": {
            "type": "array",
            "description": "Product criteria array. **Required.**\n\nEach criterion is an object with:\n- `id` — Either the XML key (e.g. `\"TypeBien\"`) or numeric ID (e.g. `\"27\"`).\n  Both are resolved by the validator.\n- `value` — Format depends on criteria type:\n  - UNIQUE: exact **value code** string from the referential (the `model` field\n    from `GET /criterias/product/{id}/values`, e.g. `\"Appartement\"`, NOT the\n    numeric ID like `\"1\"`)\n  - NUMBER: numeric value as string (e.g. `\"85.5\"`)\n  - FLAG: `0` or `1`\n  - TEXT: free text string\n  - MULTIPLE: array of value code strings\n  - DATE: ISO 8601 datetime or `dd/MM/yyyy`\n\n**Mandatory criteria:**\n- `TypeBien` (ID 27) — Property type (e.g. `\"Appartement\"`, `\"Maison\"`)\n- `TypeTransaction` (ID 28) — Transaction type (e.g. `\"Vente\"`, `\"Location\"`)\n- `CPVille` (ID 65) — Or alternatively `CodePostal` + `Ville` (auto-composed)\n\n**Recommended criteria:**\n- `Statut` — Product status. Must be `\"EnCours\"` for the product to be active\n  and visible. If omitted, the site's default applies (which may create an\n  inactive product).\n\n**DPE auto-calculation:** If you provide `conso_energ` (numeric) without\n`ConsoEner` (letter), the system auto-calculates the DPE letter based on\nproperty type and country. Same for `valeur_GES` → `GES`.\n\n**GPS auto-geocoding:** If you provide `Adresse` + `CodePostal` + `Ville`\nwithout `Alentour`, the system geocodes the address automatically.\nProvide `Alentour` directly (format `\"lat,lng\"`) to bypass geocoding.\n\nUse `GET /criterias/product/all` to discover available criteria and\n`GET /criterias/product/{id}/values` for valid values.\n",
            "items": {
              "type": "object",
              "required": [
                "id",
                "value"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Criteria identifier (XML key or numeric ID).",
                  "example": "TypeBien"
                },
                "value": {
                  "description": "Criteria value (format depends on type).",
                  "example": "Appartement"
                }
              }
            },
            "example": [
              {
                "id": "TypeBien",
                "value": "Appartement"
              },
              {
                "id": "TypeTransaction",
                "value": "Vente"
              },
              {
                "id": "Statut",
                "value": "EnCours"
              },
              {
                "id": "NbPieces",
                "value": "4"
              },
              {
                "id": "Surface",
                "value": "85.5"
              },
              {
                "id": "Prix",
                "value": "350000"
              },
              {
                "id": "conso_energ",
                "value": "125"
              },
              {
                "id": "Adresse",
                "value": "12 rue de la Paix"
              },
              {
                "id": "CodePostal",
                "value": "75002"
              },
              {
                "id": "Ville",
                "value": "Paris"
              }
            ]
          },
          "rooms": {
            "type": [
              "array",
              "null"
            ],
            "description": "Room list. Each room requires a `type` from referential list 1.\nUse `GET /configs/listes/1/values` to discover room types.\n\n| Field | Required | Validation | Description |\n|-------|----------|------------|-------------|\n| `type` | Yes | list 1 | Room type (e.g. \"Chambre\", \"Cuisine\") |\n| `niveau` | No | numeric | Floor level |\n| `surface` | No | numeric | Surface in m² |\n| `exposition` | No | list 2 | Exposure (Nord, Sud, Est, Ouest...) |\n| `vue` | No | list 3 | View (Jardin, Mer, Rue...) |\n| `sol` | No | list 4 | Floor type (Parquet, Carrelage...) |\n| `commentaire` | No | string | Free comment |\n",
            "items": {
              "type": "object",
              "required": [
                "type"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "description": "Room type from list 1.",
                  "example": "Chambre"
                },
                "niveau": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Floor level."
                },
                "surface": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Surface in m².",
                  "example": 14.5
                },
                "exposition": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Exposure from list 2."
                },
                "vue": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "View from list 3."
                },
                "sol": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Floor type from list 4."
                },
                "commentaire": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Free comment."
                }
              }
            }
          },
          "customer_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Existing customer ID to link as the property owner/seller.\nMutually exclusive with `customer` object.\n",
            "example": 789
          },
          "customer": {
            "type": "object",
            "description": "Create a new customer and link as owner. Used when `customer_id` is not provided.\n"
          }
        }
      },
      "ProductSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "price": {
            "type": [
              "number",
              "null"
            ]
          },
          "agency_id": {
            "type": "integer"
          },
          "model": {
            "type": [
              "string",
              "null"
            ]
          },
          "image": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_modified": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Product": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "model": {
            "type": [
              "string",
              "null"
            ],
            "description": "Product reference"
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "price": {
            "type": [
              "number",
              "null"
            ]
          },
          "userId": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Assigned user (agent) ID"
          },
          "customerId": {
            "type": [
              "integer",
              "null"
            ]
          },
          "agencyId": {
            "type": "integer",
            "description": "Agency ID"
          },
          "parentId": {
            "type": [
              "integer",
              "null"
            ]
          },
          "parentId2": {
            "type": [
              "integer",
              "null"
            ]
          },
          "statusWeb": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "statusVitrine": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "statusPrice": {
            "type": [
              "string",
              "null"
            ]
          },
          "image": {
            "type": [
              "string",
              "null"
            ],
            "description": "Main photo URL"
          },
          "feesSellerPercent": {
            "type": [
              "number",
              "null"
            ]
          },
          "feesBuyerPercent": {
            "type": [
              "number",
              "null"
            ]
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "dateAvailability": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "nextContact": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "gav": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "selection": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "color": {
            "type": [
              "string",
              "null"
            ]
          },
          "intercabinet": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "sellerStatistics": {
            "type": [
              "object",
              "null"
            ]
          },
          "descriptions": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "language_code": {
                  "type": "string"
                }
              }
            }
          },
          "criteresText": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object"
            }
          },
          "criteresFullText": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object"
            }
          },
          "criteresNumber": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object"
            }
          },
          "criteresFlag": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object"
            }
          },
          "rooms": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object"
            }
          },
          "photos": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string"
                },
                "title": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "customer": {
            "type": [
              "object",
              "null"
            ]
          }
        }
      },
      "UpdateProductRequest": {
        "type": "object",
        "required": [
          "product_id"
        ],
        "description": "Partial product update. Only provided fields are modified.\nThe `product_id` field is **required** in the body for scope verification\n(must match the path parameter value).\n",
        "properties": {
          "product_id": {
            "type": "integer",
            "description": "Product ID (must match path parameter). Required for scope validation.\n",
            "example": 12345
          },
          "agency_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Transfer product to another agency (must remain in scope).",
            "example": 456
          },
          "model": {
            "type": [
              "string",
              "null"
            ],
            "description": "Change product reference.",
            "example": "REF-2025-002"
          },
          "user_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Change assigned agent.",
            "example": 789
          },
          "intercabinet": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Enable/disable inter-agency sharing."
          },
          "color": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 5,
            "description": "Color code (1-5) for visual categorization."
          },
          "next_contact": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Next follow-up date (format `YYYY-MM-DD`).",
            "example": "2026-02-15"
          },
          "seller_statistics": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Enable/disable seller statistics."
          },
          "web_status": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Enable/disable web publication."
          },
          "rooms": {
            "type": [
              "array",
              "null"
            ],
            "description": "Room list update. Include `id` to update existing rooms, omit `id` to add new ones.\nIn update mode, `vue` and `sol` are **mutually exclusive** on a given room.\n",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Existing room ID (for update). Omit for new rooms."
                },
                "type": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Room type from list 1 (nullable in update)."
                },
                "niveau": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "surface": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "exposition": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "vue": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "View (mutually exclusive with `sol` in update)."
                },
                "sol": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Floor type (mutually exclusive with `vue` in update)."
                },
                "commentaire": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "descriptions": {
            "type": [
              "array",
              "null"
            ],
            "description": "Web descriptions (same format as create).",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "description": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "language_code": {
                  "type": "string"
                }
              }
            }
          },
          "criteria": {
            "type": [
              "array",
              "null"
            ],
            "description": "Criteria to update. Only provided criteria are modified — existing\ncriteria not in this array are **preserved** (not reset).\nFormat is same as create, except `value` can be `null` (to clear a criterion).\n",
            "items": {
              "type": "object",
              "required": [
                "id"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Criteria identifier (XML key or numeric ID)."
                },
                "value": {
                  "description": "New value (null to clear)."
                }
              }
            }
          }
        }
      },
      "ProductListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "customers_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "parent_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "parent_id2": {
            "type": [
              "integer",
              "null"
            ]
          },
          "quantity": {
            "type": "integer"
          },
          "model": {
            "type": [
              "string",
              "null"
            ]
          },
          "status_web": {
            "type": "boolean"
          },
          "image": {
            "type": [
              "string",
              "null"
            ]
          },
          "agency_id": {
            "type": "integer"
          },
          "price": {
            "type": [
              "number",
              "null"
            ]
          },
          "fees_seller": {
            "type": [
              "number",
              "null"
            ]
          },
          "fees_buyer": {
            "type": [
              "number",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_modified": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "date_availability": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "next_contact": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "gav": {
            "type": "boolean"
          },
          "products_selection": {
            "type": "boolean"
          },
          "category": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "integer"
              },
              "parent_id": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "order": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "matching_criteres": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "parent": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "Recursive parent category"
              }
            }
          },
          "themes": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "CriteriaResource": {
        "type": "object",
        "properties": {
          "xml": {
            "type": [
              "string",
              "null"
            ],
            "description": "Criteria XML identifier"
          },
          "id": {
            "type": "integer",
            "description": "Criteria ID"
          },
          "priority": {
            "type": [
              "integer",
              "null"
            ]
          },
          "type": {
            "type": [
              "string",
              "null"
            ],
            "description": "Criteria type (text, number, flag, etc.)"
          },
          "label": {
            "type": [
              "string",
              "null"
            ],
            "description": "Localized label"
          },
          "personalizable": {
            "type": "boolean"
          },
          "is_visible": {
            "type": "boolean",
            "description": "Whether the criteria is visible for the current editeur"
          },
          "is_activated": {
            "type": "boolean"
          },
          "is_confidential": {
            "type": "boolean"
          },
          "is_confidential_disabled": {
            "type": "boolean"
          },
          "is_activated_disabled": {
            "type": "boolean"
          },
          "is_fiche_co_visible": {
            "type": "boolean"
          },
          "is_fiche_co_visible_disabled": {
            "type": "boolean"
          },
          "is_activated_form_seller": {
            "type": "boolean"
          }
        }
      },
      "CustomerPhoneBody": {
        "type": "object",
        "required": [
          "slot",
          "number"
        ],
        "description": "A phone entry addressed by its slot. Mirrors the `phones` array returned\nby the API (in read, `type` is a `{id, label}` object; in write, provide\n`type_id`).\n",
        "properties": {
          "slot": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4,
            "description": "Phone slot (1-4).",
            "example": 1
          },
          "number": {
            "type": [
              "string",
              "null"
            ],
            "example": "+33201010101"
          },
          "type_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Phone type ID."
          },
          "stop_sms": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "SMS opt-out flag for this number.",
            "example": false
          }
        }
      },
      "CustomerAddressBody": {
        "type": "object",
        "description": "Customer address block.\n",
        "properties": {
          "street": {
            "type": [
              "string",
              "null"
            ],
            "example": "105 Rue Charles Nungesser"
          },
          "complement": {
            "type": [
              "string",
              "null"
            ]
          },
          "postcode": {
            "type": [
              "string",
              "null"
            ],
            "example": "29490"
          },
          "city": {
            "type": [
              "string",
              "null"
            ],
            "example": "Guipavas"
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "description": "Country name or ISO 3166-1 alpha-2 code (e.g. `FR`).",
            "example": "FR"
          },
          "residence": {
            "type": [
              "string",
              "null"
            ]
          },
          "latitude": {
            "type": [
              "string",
              "null"
            ],
            "example": "48.44145"
          },
          "longitude": {
            "type": [
              "string",
              "null"
            ],
            "example": "-4.415114"
          }
        }
      },
      "CustomerSubscriptionsField": {
        "type": [
          "array",
          "null"
        ],
        "description": "Customer alert subscriptions. Each item controls a specific subscription channel.\n",
        "items": {
          "type": "object",
          "required": [
            "id",
            "active"
          ],
          "properties": {
            "id": {
              "type": "string",
              "enum": [
                "proposition_bien",
                "abonne_newsletter",
                "customers_partenaires"
              ],
              "description": "Available subscriptions: * `proposition_bien` - Accepts property proposals * `abonne_newsletter` - Accepts newsletters * `customers_partenaires` - Accepts partner emails\n"
            },
            "active": {
              "type": "boolean",
              "description": "Whether this subscription is active."
            }
          }
        },
        "example": [
          {
            "id": "proposition_bien",
            "active": true
          },
          {
            "id": "abonne_newsletter",
            "active": false
          }
        ]
      },
      "CreateCustomerRequest": {
        "type": "object",
        "required": [
          "email"
        ],
        "description": "Creates a customer. `email` is required and at least one of `first_name`,\n`last_name` or `business_name` must be provided. Duplicate detection is\nperformed on **email OR phones**.\n",
        "properties": {
          "gender": {
            "type": [
              "string",
              "null"
            ],
            "description": "Customer gender code. Use `GET /users/genders` for available values.",
            "example": "m"
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Jean"
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Dupont"
          },
          "business_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Company name (for professional customers)."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Customer email address. **Required.**",
            "example": "jean.dupont@example.com"
          },
          "phones": {
            "type": [
              "array",
              "null"
            ],
            "description": "Phone numbers, one entry per slot (1-4). `slot` and `number` are required and\nidentifies the position; `type_id`, and `stop_sms` are optional.\n",
            "items": {
              "$ref": "#/components/schemas/CustomerPhoneBody"
            }
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use the `phones` array (slot 1).",
            "example": "+33201010101"
          },
          "mobile_phone": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use the `phones` array (slot 3).",
            "example": "+33601010101"
          },
          "origin_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Customer origin ID. Use `GET /customers/origins`.",
            "example": 12
          },
          "assigned_user_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Assigned negotiator (agent) ID.",
            "example": 123456
          },
          "agency_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Agency ID (within the authenticated site's scope).",
            "example": 123456
          },
          "next_contact_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal reference of the customer."
          },
          "comments": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal notes"
          },
          "current_situation_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Current situation list value ID (liste 5)."
          },
          "group": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Customer group ID. Use `GET /customers/groups`.",
            "example": 1
          },
          "groups": {
            "type": [
              "array",
              "null"
            ],
            "description": "Array of customer group IDs. Takes precedence over `group`.",
            "items": {
              "type": "integer"
            },
            "example": [
              1,
              2
            ]
          },
          "address": {
            "$ref": "#/components/schemas/CustomerAddressBody"
          },
          "newsletter": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether or not the customer agrees to receive communications by email."
          },
          "subscriptions": {
            "$ref": "#/components/schemas/CustomerSubscriptionsField"
          },
          "rgpd_consent": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "GDPR consent flag."
          },
          "color_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Customer color ID."
          },
          "notary_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Notary customer ID.",
            "example": 123456789
          },
          "check_duplicate": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "- `false` or omitted: silently updates an existing duplicate and returns its ID.\n- `true`: returns HTTP 409 when a duplicate is found.\n",
            "example": true
          },
          "firstname": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `first_name`."
          },
          "lastname": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `last_name`."
          },
          "user_id": {
            "type": [
              "integer",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `assigned_user_id`."
          },
          "type_id": {
            "type": [
              "integer",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `current_situation_id`."
          }
        }
      },
      "SearchCustomerRequest": {
        "type": "object",
        "description": "Search filters. All fields are optional. When no filters are provided,\nreturns all customers in scope (paginated).\n",
        "properties": {
          "first_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Filter by first name (partial match).",
            "example": "Jean"
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Filter by last name (partial match).",
            "example": "Dupont"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Filter by email (exact match).",
            "example": "jean.dupont@example.com"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Filter by phone number (partial match).",
            "example": "0601010101"
          },
          "agency_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Filter by agency ID.",
            "example": 263961
          },
          "group_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Filter by customer group ID.",
            "example": 1
          },
          "origin_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Filter by customer origin ID.",
            "example": 12
          },
          "per_page": {
            "type": "integer",
            "minimum": 1,
            "maximum": 200,
            "default": 50,
            "description": "Number of results per page. Maximum 200.",
            "example": 25
          },
          "cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Opaque pagination cursor from the previous response's `meta.next_cursor`.\nOmit for the first page.\n"
          },
          "firstname": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `first_name`."
          },
          "lastname": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `last_name`."
          }
        }
      },
      "CustomerGroupResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Group ID.",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Group display name.",
            "example": "Demandeur"
          }
        }
      },
      "CustomerConsentResponse": {
        "type": "object",
        "description": "Active cold-calling consent for the customer.",
        "properties": {
          "reason": {
            "type": "string",
            "description": "Reason/motive for the consent.",
            "example": "Accord verbal lors de la visite"
          },
          "accept_outside_hours": {
            "type": "boolean",
            "description": "Whether the customer accepts being contacted outside business hours.",
            "example": true
          },
          "consent_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Date when consent was given.",
            "example": "2026-06-15T10:00:00+02:00"
          },
          "proofs": {
            "type": "array",
            "description": "Proof files stored in cloud storage.\nUse `GET /mods/immocloud/files/download?type=contact&customer_id={id}&folder={folder}&file={file}`\nto download a proof file.\n",
            "items": {
              "type": "object",
              "properties": {
                "folder": {
                  "type": "string",
                  "description": "Cloud storage folder path.",
                  "example": "Private/Consentement"
                },
                "file": {
                  "type": "string",
                  "description": "Filename in cloud storage.",
                  "example": "formulaire-signe-668a3f2e1b4c7.pdf"
                }
              }
            }
          }
        }
      },
      "SearchCriteriaResponse": {
        "type": "object",
        "description": "A single search criterion.",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Criteria ID."
          },
          "operator": {
            "type": [
              "string",
              "null"
            ],
            "description": "Comparison operator (e.g. EGAL, SUPERIEUR, CONTIENT)."
          },
          "value": {
            "description": "Value(s). A string, or an array of strings for multi-value criteria.\n"
          }
        }
      },
      "SearchPolygonResponse": {
        "type": "object",
        "description": "A geographic search polygon.",
        "properties": {
          "type": {
            "type": [
              "string",
              "null"
            ],
            "description": "Search type (time | distance | free)."
          },
          "mode": {
            "type": [
              "string",
              "null"
            ],
            "description": "Travel mode."
          },
          "start": {
            "type": [
              "string",
              "null"
            ],
            "description": "Starting point."
          },
          "value": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Value in km or minutes."
          },
          "polygon": {
            "description": "Polygon coordinates."
          }
        }
      },
      "SearchRequestResponse": {
        "type": "object",
        "description": "A customer search request. Reference fields (`intended_use`,\n`reason_for_purchase`, `current_resale`, `timeframe`) are `{id, label}`\nobjects loaded on demand. Response keys follow `snake_case`; camelCase\nkeys are deprecated.\n",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "comment": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_active": {
            "type": "boolean"
          },
          "has_email_alert": {
            "type": "boolean"
          },
          "has_sms_alert": {
            "type": "boolean"
          },
          "intended_use": {
            "type": [
              "object",
              "null"
            ],
            "description": "Intended use (`{id, label}`)."
          },
          "reason_for_purchase": {
            "type": [
              "object",
              "null"
            ],
            "description": "Reason for purchase (`{id, label}`)."
          },
          "current_resale": {
            "type": [
              "object",
              "null"
            ],
            "description": "Current resale (`{id, label}`)."
          },
          "timeframe": {
            "type": [
              "object",
              "null"
            ],
            "description": "Timeframe (`{id, label}`)."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "criteria": {
            "type": "array",
            "description": "Search criteria (internal `COMPRIS` bounding rows excluded).",
            "items": {
              "$ref": "#/components/schemas/SearchCriteriaResponse"
            }
          },
          "key_points": {
            "type": "object",
            "description": "Key points split into pros and cons.",
            "properties": {
              "pros": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/SearchCriteriaResponse"
                }
              },
              "cons": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/SearchCriteriaResponse"
                }
              }
            }
          },
          "polygons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchPolygonResponse"
            }
          },
          "isActive": {
            "type": "boolean",
            "deprecated": true,
            "description": "**Deprecated.** Use `is_active`."
          },
          "hasEmailAlert": {
            "type": "boolean",
            "deprecated": true,
            "description": "**Deprecated.** Use `has_email_alert`."
          },
          "hasSmsAlert": {
            "type": "boolean",
            "deprecated": true,
            "description": "**Deprecated.** Use `has_sms_alert`."
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "deprecated": true,
            "description": "**Deprecated.** Use `created_at`."
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "deprecated": true,
            "description": "**Deprecated.** Use `updated_at`."
          }
        }
      },
      "CustomerRelationResponse": {
        "type": "object",
        "description": "A customer linked to another customer.\n",
        "properties": {
          "id": {
            "type": "integer",
            "description": "ID of the related customer.",
            "example": 123456789
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Marie"
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Dupont"
          },
          "business_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Company name of the related customer (for professional customer)."
          },
          "role": {
            "type": "object",
            "description": "Role of the related customer within the relationship.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Role list value ID (liste 17).",
                "example": 29
              },
              "label": {
                "type": "string",
                "example": "Épouse"
              }
            }
          },
          "keep_informed": {
            "type": "boolean",
            "description": "Whether this related customer should be kept informed.",
            "example": true
          },
          "is_joint_ownership": {
            "type": "boolean",
            "description": "Whether the relationship is a joint ownership (indivision).",
            "example": false
          },
          "joint_ownership": {
            "type": [
              "object",
              "null"
            ],
            "description": "Joint ownership details, or `null` when the relationship is not a\njoint ownership.\n",
            "properties": {
              "share_percent": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Ownership share (percentage).",
                "example": "50"
              }
            }
          }
        }
      },
      "CustomerResponse": {
        "type": "object",
        "description": "Customer details. Optional relations are loaded via the `?include=` parameter\nand are absent from the response otherwise.\n\nResponse keys follow `snake_case`. Some camelCase keys are still returned but\ndeprecated (see the changelog); use their snake_case equivalents.\n",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique customer identifier.",
            "example": 789
          },
          "gender": {
            "type": "object",
            "description": "Gender details (or the raw gender code when not loaded). Loaded with `include=gender`."
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Jean"
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Dupont"
          },
          "business_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Company name (for professional customers)."
          },
          "firstname": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `first_name`."
          },
          "lastname": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `last_name`."
          },
          "companyName": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `business_name`."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "example": "jean.dupont@example.com"
          },
          "phones": {
            "type": "array",
            "description": "Phone numbers (read-only). Each entry has a stable `slot` (1-4), the\n`number`, its `type` (`{id, label}`), and the `stop_sms` flag. Empty\nslots are omitted.\n",
            "items": {
              "type": "object",
              "properties": {
                "slot": {
                  "type": "integer",
                  "description": "Phone slot (1-4).",
                  "example": 1
                },
                "number": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "example": "+33201010101"
                },
                "type": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "id": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "Phone type ID."
                    },
                    "label": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Phone type label."
                    }
                  }
                },
                "stop_sms": {
                  "type": "boolean",
                  "description": "SMS opt-out flag for this number. Loaded with `include=stopSms`."
                }
              }
            }
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use the `phones` array.",
            "example": "+33201010101"
          },
          "mobilePhone": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use the `phones` array.",
            "example": "+33601010101"
          },
          "origin": {
            "type": "object",
            "description": "Customer acquisition origin. Loaded with `include=origin`."
          },
          "assigned_user": {
            "type": [
              "object",
              "null"
            ],
            "description": "Assigned negotiator. Full object when `include=user`, otherwise `{ id }`,\nor `null` when no negotiator is assigned.\n"
          },
          "created_by": {
            "type": [
              "object",
              "null"
            ],
            "description": "Creator of the customer. `{ id }` or full object with `include=user`, `null` if none."
          },
          "agency": {
            "type": "object",
            "description": "Agency. Loaded with `include=agency`."
          },
          "next_contact_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Date of the next planned contact."
          },
          "reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal reference of the customer."
          },
          "created_via": {
            "type": [
              "object",
              "null"
            ],
            "description": "Technical creation channel (`{id, label}`).\n"
          },
          "informations": {
            "type": "object",
            "deprecated": true,
            "description": "**Deprecated.** Use `created_via`."
          },
          "comments": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free-text comments about the customer."
          },
          "current_situation": {
            "type": [
              "object",
              "null"
            ],
            "description": "Current situation of the customer (`{id, label}`)."
          },
          "type": {
            "type": [
              "integer",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `current_situation`."
          },
          "groups": {
            "type": "array",
            "description": "Customer groups. Loaded with `include=groups`.",
            "items": {
              "$ref": "#/components/schemas/CustomerGroupResponse"
            }
          },
          "address": {
            "type": "array",
            "description": "Customer addresses. Loaded with `include=address`.",
            "items": {
              "type": "object"
            }
          },
          "color": {
            "type": "object",
            "description": "Customer color (`id`, `value`, `letter`, `label`). Loaded with `include=color`.\n"
          },
          "newsletter": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Newsletter subscription flag."
          },
          "subscriptions": {
            "type": "array",
            "description": "Alert subscriptions. Loaded with `include=subscriptions`.",
            "items": {
              "type": "object"
            }
          },
          "rgpd_consent": {
            "type": "boolean",
            "description": "GDPR consent flag (customers.rgpd column)."
          },
          "rgpd": {
            "type": "boolean",
            "description": "GDPR consent data. Loaded with include=rgpd.\n"
          },
          "consent": {
            "$ref": "#/components/schemas/CustomerConsentResponse"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2025-01-15T10:30:00+01:00"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "deprecated": true,
            "description": "**Deprecated.** Use `created_at`."
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "deprecated": true,
            "description": "**Deprecated.** Use `updated_at`."
          },
          "notary": {
            "type": [
              "object",
              "null"
            ],
            "description": "Notary customer. Loaded with `include=notary`."
          },
          "mandate": {
            "type": "array",
            "description": "Registered mandates. Loaded with `include=mandate`.",
            "items": {
              "type": "object"
            }
          },
          "website": {
            "type": "object",
            "description": "Client area access (`is_access_granted`, `url`, `last_login_at`,\n`login_count`, `account_created_at`, `account_updated_at`). Loaded with `include=website`.\n"
          },
          "search_requests": {
            "type": "array",
            "description": "Search requests. Loaded with `include=searchRequests`.",
            "items": {
              "$ref": "#/components/schemas/SearchRequestResponse"
            }
          },
          "relations": {
            "type": "array",
            "description": "Related customers. Loaded with `include=relations`.",
            "items": {
              "$ref": "#/components/schemas/CustomerRelationResponse"
            }
          },
          "typeProject": {
            "type": "object",
            "deprecated": true,
            "description": "**Deprecated.** Moved to the search request level (`intended_use`)."
          },
          "stopSms": {
            "type": "object",
            "deprecated": true,
            "description": "**Deprecated.** Use the `phones` array with `include=stopSms`."
          },
          "searchRequests": {
            "type": "array",
            "deprecated": true,
            "description": "**Deprecated.** Use `search_requests`.",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "CursorPaginationMeta-2": {
        "type": "object",
        "description": "Pagination metadata for cursor-based listings.",
        "properties": {
          "per_page": {
            "type": "integer",
            "description": "Number of items returned per page.",
            "example": 50
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor to pass in the next request to fetch the next page.\nNull when there is no next page.\n",
            "example": "eyJpZCI6MTIzfQ=="
          },
          "prev_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor for the previous page. Null on first page."
          }
        }
      },
      "CursorPaginationLinks-2": {
        "type": "object",
        "description": "Ready-to-use links for cursor-based navigation.",
        "properties": {
          "next": {
            "type": [
              "string",
              "null"
            ],
            "description": "URL of the next page, or null."
          },
          "prev": {
            "type": [
              "string",
              "null"
            ],
            "description": "URL of the previous page, or null."
          }
        }
      },
      "CustomerOriginResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Origin ID.",
            "example": 12
          },
          "label": {
            "type": "string",
            "description": "Origin display label.",
            "example": "Site internet"
          }
        }
      },
      "UpdateCustomerRequest": {
        "type": "object",
        "description": "Partial update of a customer. Only provided fields are modified.\n",
        "properties": {
          "gender": {
            "type": [
              "string",
              "null"
            ],
            "description": "Customer gender code. Use `GET /users/genders` for available values.",
            "example": "m"
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Jean"
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Dupont"
          },
          "business_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Company name (for professional customers)."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "description": "Customer email address.",
            "example": "new.email@example.com"
          },
          "phones": {
            "type": [
              "array",
              "null"
            ],
            "description": "Phone numbers, one entry per slot (1-4). `slot` is required and\nidentifies the position; `number`, `type_id`, and `stop_sms` are optional.\n",
            "items": {
              "$ref": "#/components/schemas/CustomerPhoneBody"
            }
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use the `phones` array (slot 1).",
            "example": "+33201020304"
          },
          "mobile_phone": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use the `phones` array (slot 3).",
            "example": "+33601020304"
          },
          "origin_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Customer origin ID. Use `GET /customers/origins`.",
            "example": 12
          },
          "assigned_user_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Assigned negotiator (agent) ID.",
            "example": 123456
          },
          "agency_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Agency ID (within the authenticated site's scope) to reassign the customer to.",
            "example": 123456
          },
          "next_contact_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal reference of the customer."
          },
          "comments": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal notes"
          },
          "current_situation_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Current situation list value ID (liste 5)."
          },
          "group": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Customer group ID. Use `GET /customers/groups`.",
            "example": 2
          },
          "groups": {
            "type": [
              "array",
              "null"
            ],
            "description": "Array of customer group IDs. Takes precedence over `group`.",
            "items": {
              "type": "integer"
            },
            "example": [
              2,
              4
            ]
          },
          "address": {
            "$ref": "#/components/schemas/CustomerAddressBody"
          },
          "newsletter": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether or not the customer agrees to receive communications by email."
          },
          "subscriptions": {
            "$ref": "#/components/schemas/CustomerSubscriptionsField"
          },
          "rgpd_consent": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "GDPR consent flag."
          },
          "color_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Customer color ID."
          },
          "notary_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Notary customer ID.",
            "example": 123456789
          },
          "firstname": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `first_name`."
          },
          "lastname": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `last_name`."
          },
          "user_id": {
            "type": [
              "integer",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `assigned_user_id`."
          },
          "type_id": {
            "type": [
              "integer",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `current_situation_id`."
          },
          "product_id": {
            "type": [
              "integer",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Product to associate via a follow-up.",
            "example": 123456789
          }
        }
      },
      "UpdateCustomerConsentRequest": {
        "type": "object",
        "required": [
          "reason"
        ],
        "description": "Request body for creating or updating cold-calling consent.\nUse `multipart/form-data` encoding when attaching proof files.\n",
        "properties": {
          "reason": {
            "type": "string",
            "maxLength": 64,
            "description": "Reason/motive for the consent (e.g. \"Accord verbal\", \"Formulaire signé\").",
            "example": "Accord verbal lors de la visite"
          },
          "accept_outside_hours": {
            "type": "boolean",
            "description": "Whether the customer accepts being contacted outside business hours.",
            "example": true
          },
          "proofs": {
            "type": [
              "array",
              "null"
            ],
            "description": "Proof files (scanned documents, signed forms, etc.).\nFiles are uploaded to the customer's cloud storage under `Documents confidentiels/Consentement` subfolder.\nOnly used with `multipart/form-data` content type.\n",
            "items": {
              "type": "string",
              "format": "binary"
            }
          },
          "consent_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Date when consent was given. ISO 8601 format.\nDefaults to current date/time when omitted.\n",
            "example": "2026-06-15T10:00:00+02:00"
          }
        }
      },
      "CreateSearchRequestBody": {
        "type": "object",
        "required": [
          "criteria"
        ],
        "description": "Creates a search request (buyer search criteria) for a customer.\nUse `GET /criterias/search-requests` to discover available criteria for your site.\n",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Label for the search request.",
            "example": "Apartment T3 Paris 15 budget 400k"
          },
          "comment": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal note about this search request.",
            "example": "Client flexible on surface, priority is location near metro"
          },
          "is_active": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the search request is active.",
            "example": true
          },
          "has_email_alert": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Send email alerts when matching properties are found.",
            "example": true
          },
          "has_sms_alert": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Send SMS alerts when matching properties are found.",
            "example": false
          },
          "intended_use_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Intended use."
          },
          "reason_for_purchase_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Reason-for-purchase list value ID (liste 35)."
          },
          "current_resale_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Current-resale list value ID (liste 77)."
          },
          "timeframe_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Timeframe list value ID (liste 4)."
          },
          "keyword": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true
          },
          "wording": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `name`."
          },
          "alertEmail": {
            "type": [
              "boolean",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `has_email_alert`."
          },
          "alertSms": {
            "type": [
              "boolean",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `has_sms_alert`."
          },
          "criteria": {
            "type": "array",
            "description": "Array of search criteria. Each criterion has an `id`, an `operator`, and a `value`.\n\n**Operators:**\n| Operator | Description | Example use |\n|----------|-------------|-------------|\n| `EGAL` | Equals (exact match) | Transaction type, property category |\n| `SUPERIEUR` | Greater than or equal | Min surface, min rooms |\n| `INFERIEUR` | Less than or equal | Max price, max floor |\n| `CONTIENT` | Contains (multi-value) | Zones, cities |\n| `DIFFERENT` | Not equal | Exclude a type |\n\n**Common criteria IDs** (use `GET /criterias/search-requests` for the full list):\n| ID | Description | Type |\n|----|-------------|------|\n| 27 | Property category | UNIQUE |\n| 28 | Transaction type | UNIQUE |\n| 65 | Postcode City | TEXT |\n| 30 | Price | NUMBER |\n| 33 | Number of rooms | NUMBER |\n| 34 | Living area (m²) | NUMBER |\n| 38 | Bedrooms | NUMBER |\n",
            "items": {
              "type": "object",
              "required": [
                "id",
                "operator",
                "value"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Criteria ID (numeric string or XML key).\nUse `GET /criterias/search-requests` to list available IDs.\n",
                  "example": "27"
                },
                "operator": {
                  "type": "string",
                  "enum": [
                    "CONTIENT",
                    "SUPERIEUR",
                    "INFERIEUR",
                    "EGAL",
                    "DIFFERENT"
                  ],
                  "description": "Comparison operator.",
                  "example": "EGAL"
                },
                "value": {
                  "description": "Value to compare. Format depends on criteria type.\nFor operator \"CONTIENT\", multiple values can be comma-separated.\n",
                  "example": "1"
                }
              }
            },
            "example": [
              {
                "id": "27",
                "operator": "EGAL",
                "value": "1"
              },
              {
                "id": "28",
                "operator": "EGAL",
                "value": "Vente"
              },
              {
                "id": "30",
                "operator": "INFERIEUR",
                "value": "400000"
              },
              {
                "id": "33",
                "operator": "EGAL",
                "value": "3"
              },
              {
                "id": "65",
                "operator": "EGAL",
                "value": "75015 PARIS"
              }
            ]
          }
        }
      },
      "UpdateSearchRequestBody": {
        "type": "object",
        "description": "Partial update. Only provided fields are modified.\nWhen `criteria` is provided, it **replaces** the entire criteria list (not merged).\n",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Label for the search request.",
            "example": "Apartment T3 Paris 15 budget 400k"
          },
          "comment": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal note about this search request.",
            "example": "Client flexible on surface, priority is location near metro"
          },
          "is_active": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the search request is active.",
            "example": true
          },
          "has_email_alert": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Send email alerts when matching properties are found.",
            "example": true
          },
          "has_sms_alert": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Send SMS alerts when matching properties are found.",
            "example": false
          },
          "intended_use_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Intended use."
          },
          "reason_for_purchase_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Reason-for-purchase list value ID (liste 35)."
          },
          "current_resale_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Current-resale list value ID (liste 77)."
          },
          "timeframe_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Timeframe list value ID (liste 4)."
          },
          "keyword": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true
          },
          "wording": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `name`."
          },
          "alertEmail": {
            "type": [
              "boolean",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `has_email_alert`."
          },
          "alertSms": {
            "type": [
              "boolean",
              "null"
            ],
            "deprecated": true,
            "description": "**Deprecated.** Use `has_sms_alert`."
          },
          "criteria": {
            "type": [
              "array",
              "null"
            ],
            "description": "Replacement criteria array. Same format as create.\n**Replaces** all existing criteria when provided.\n",
            "items": {
              "type": "object",
              "required": [
                "id",
                "operator",
                "value"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "operator": {
                  "type": "string",
                  "enum": [
                    "CONTIENT",
                    "SUPERIEUR",
                    "INFERIEUR",
                    "EGAL",
                    "DIFFERENT"
                  ]
                },
                "value": {
                  "description": "Value depends on criteria type."
                }
              }
            }
          }
        }
      },
      "FollowUpResponse": {
        "type": "object",
        "description": "A follow-up links a customer to a product they are interested in.",
        "properties": {
          "customerId": {
            "type": "integer",
            "description": "Customer ID.",
            "example": 789
          },
          "productId": {
            "type": "integer",
            "description": "Product ID.",
            "example": 12345
          },
          "opinion": {
            "type": "object",
            "description": "Agent's opinion on the match.",
            "properties": {
              "value": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "text": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "rating": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 5,
            "description": "Customer interest rating (0-5).",
            "example": 4
          },
          "visibleCustomer": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the follow-up is visible to the customer (portal)."
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Last update timestamp."
          },
          "progress": {
            "type": [
              "string",
              "null"
            ],
            "description": "Follow-up progress label."
          }
        }
      },
      "UpdateFollowUpRequest": {
        "type": "object",
        "description": "Update fields for a follow-up.",
        "properties": {
          "rating": {
            "type": "integer",
            "minimum": 0,
            "maximum": 5,
            "description": "Customer interest rating for this product (0 = not rated, 1-5 scale).\n",
            "example": 4
          }
        }
      },
      "ActionResponse": {
        "type": "object",
        "description": "A logged commercial action (call, visit, email, etc.).",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Action log entry ID.",
            "example": 98765
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Action type description/label.",
            "example": "Appel sortant"
          },
          "date_performed": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Date when the action was performed.",
            "example": "2026-01-15T14:30:00+01:00"
          },
          "result": {
            "type": [
              "string",
              "null"
            ],
            "description": "Action result/comment.",
            "example": "Client intéressé"
          },
          "user_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Agent who performed the action.",
            "example": 456
          }
        }
      },
      "LeadCustomer": {
        "type": "object",
        "required": [
          "email"
        ],
        "description": "Customer data for the lead. Uses **snake_case** field names.\nDuplicate detection is performed on email OR phone OR mobile_phone.\n",
        "properties": {
          "firstname": {
            "type": [
              "string",
              "null"
            ],
            "description": "Customer first name.",
            "example": "Marie"
          },
          "lastname": {
            "type": [
              "string",
              "null"
            ],
            "description": "Customer last name.",
            "example": "Martin"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Customer email address. **Required.**\nUsed (along with phone and mobile_phone) for duplicate detection.\n",
            "example": "marie.martin@example.com"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Phone number. International format recommended (e.g. `+33601020304`).\nValidated with libphonenumber.\n",
            "example": "+33698765432"
          },
          "phone2": {
            "type": [
              "string",
              "null"
            ],
            "description": "Secondary phone number."
          },
          "mobile_phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Mobile phone number.",
            "example": "+33611223344"
          },
          "mobile_phone2": {
            "type": [
              "string",
              "null"
            ],
            "description": "Secondary mobile phone."
          },
          "fax": {
            "type": [
              "string",
              "null"
            ],
            "description": "Fax number."
          },
          "gender": {
            "type": [
              "string",
              "null"
            ],
            "description": "Customer gender code. Use `GET /users/genders` to discover available values.\n",
            "example": "F"
          },
          "group": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Customer group ID. Seller leads accept groups in families 4,5,6,7,10,11,12.\nBuyer leads accept groups in families 1,2,3,8,9.\nIf omitted, defaults to group 2 (seller) or 1 (buyer).\n",
            "example": 4
          },
          "groups": {
            "type": [
              "array",
              "null"
            ],
            "description": "Array of customer group IDs (when multiple groups apply).\nTakes precedence over `group` when both are provided.\n",
            "items": {
              "type": "integer"
            },
            "example": [
              4,
              5
            ]
          },
          "origin": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Customer origin ID. When omitted, the system auto-assigns the origin\nconfigured for the API client (partner).\nUse `GET /customers/origins` to list available origins.\n",
            "example": 12
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "description": "Street address (number + street).",
            "example": "10 rue des Lilas"
          },
          "address_complement": {
            "type": [
              "string",
              "null"
            ],
            "description": "Address complement (building, floor, etc.)."
          },
          "postalcode": {
            "type": [
              "string",
              "null"
            ],
            "description": "Postal code.",
            "example": "69001"
          },
          "city": {
            "type": [
              "string",
              "null"
            ],
            "description": "City name.",
            "example": "Lyon"
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "description": "Country code (ISO).",
            "example": "FR"
          },
          "residence": {
            "type": [
              "string",
              "null"
            ],
            "description": "Residence/building name."
          },
          "latitude": {
            "type": [
              "string",
              "null"
            ],
            "description": "Contact address latitude (decimal degrees as string).",
            "example": "45.7640"
          },
          "longitude": {
            "type": [
              "string",
              "null"
            ],
            "description": "Contact address longitude (decimal degrees as string).",
            "example": "4.8357"
          },
          "next_contact": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Suggested next contact date (format `YYYY-MM-DD`)."
          },
          "model": {
            "type": [
              "string",
              "null"
            ],
            "description": "External customer reference (your system's ID)."
          },
          "comment2": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal comment."
          },
          "newsletter": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the customer is subscribed to the newsletter."
          },
          "rgpd": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "GDPR consent flag."
          },
          "password": {
            "type": [
              "string",
              "null"
            ],
            "description": "Customer portal password (for web access)."
          },
          "status": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Customer active status."
          }
        }
      },
      "CreateSellerLeadRequest": {
        "type": "object",
        "required": [
          "agency_id",
          "customer"
        ],
        "properties": {
          "agency_id": {
            "type": "integer",
            "description": "Target agency ID. Must be among the agencies accessible from the authenticated\nsite. Use `GET /agencies` to discover\navailable IDs.\n",
            "example": 123
          },
          "user_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Assigned user (agent) ID. When omitted, the system uses the configured\nlead distribution rule (`DEFAULT_LEADS_AFFECTATION`) to automatically assign\na user within the target agency.\n",
            "example": 456
          },
          "check_duplicate": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Controls duplicate behavior:\n- `false` or omitted (default): if a duplicate customer is found by email OR phone OR mobile_phone,\n  the existing contact is **silently updated** and linked to the product.\n- `true`: returns HTTP 409 with error code `CONFLICT` when a duplicate is found.\n"
          },
          "customer": {
            "$ref": "#/components/schemas/LeadCustomer"
          },
          "product_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Existing product ID to link the seller lead to. Mutually exclusive with\nthe `product` object — provide one or the other, not both.\nThe product must exist and be within the site scope.\n",
            "example": 99999
          },
          "product": {
            "type": "object",
            "description": "Product to create (required if `product_id` is not provided).\nCriteria follow the same rules as `POST /products`:\n- `id` accepts either the XML key (e.g. `\"TypeBien\"`) or numeric ID (e.g. `\"27\"`)\n- `value` format depends on criteria type (see Products documentation)\n- DPE auto-calculated, GPS auto-geocoded (see Products pack for details)\n",
            "properties": {
              "model": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Product reference. Auto-generated if omitted.\nUse this to store your own reference for cross-system mapping.\n"
              },
              "criteria": {
                "type": "array",
                "description": "Array of criteria for the new product. At minimum, include the mandatory\ncriteria for your site (typically `TypeBien` and `TypeTransaction`).\n",
                "items": {
                  "type": "object",
                  "required": [
                    "id",
                    "value"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Criteria identifier. Accepts either the XML key (e.g. `\"TypeBien\"`,\n`\"Surface\"`, `\"conso_energ\"`) or the numeric ID (e.g. `\"27\"`).\n",
                      "example": "TypeBien"
                    },
                    "value": {
                      "description": "Criteria value. Format depends on criteria type:\n- UNIQUE: exact value code string (e.g. `\"Appartement\"`)\n- NUMBER: numeric value as string (e.g. `\"85.5\"`)\n- FLAG: `0` or `1`\n- TEXT: free text\n- MULTIPLE: array of value code strings\n- DATE: ISO 8601 datetime or `dd/MM/yyyy`\n",
                      "example": "Maison"
                    }
                  }
                }
              },
              "rooms": {
                "type": [
                  "array",
                  "null"
                ],
                "description": "Room list for the new product. Each room has a `type` (validated against\nreferential list 1), optional `surface`, `niveau`, `exposition`, `vue`, `sol`.\nUse `GET /configs/listes/1/values` to discover room types.\n",
                "items": {
                  "type": "object",
                  "required": [
                    "type"
                  ],
                  "properties": {
                    "type": {
                      "type": "string",
                      "description": "Room type from referential list 1.",
                      "example": "Chambre"
                    },
                    "surface": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Room surface in m².",
                      "example": 14.5
                    },
                    "niveau": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Floor level."
                    },
                    "exposition": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Exposure from referential list 2."
                    },
                    "vue": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "View from referential list 3."
                    },
                    "sol": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Floor type from referential list 4."
                    },
                    "commentaire": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Free-text comment."
                    }
                  }
                }
              },
              "descriptions": {
                "type": [
                  "array",
                  "null"
                ],
                "description": "Descriptive texts for the product (multilingual).",
                "items": {
                  "type": "object",
                  "properties": {
                    "title": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Short title."
                    },
                    "description": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Full description."
                    },
                    "language_code": {
                      "type": "string",
                      "description": "ISO 639-2 language code (e.g. `\"fr\"`).",
                      "example": "fr"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "LeadSearchRequest": {
        "type": "object",
        "description": "A search request defines what the buyer is looking for. It contains\ncriteria with operators that define the search parameters.\n",
        "properties": {
          "wording": {
            "type": [
              "string",
              "null"
            ],
            "description": "Display label for the search request.",
            "example": "Appartement T3 Lyon centre"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal name/identifier for the search request."
          },
          "activated": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the search request is active. When active, the system\nruns automatic matching against new properties.\n",
            "default": true
          },
          "alert_email": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Send email alerts when matching properties are found."
          },
          "alert_sms": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Send SMS alerts when matching properties are found."
          },
          "type_projet_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Project type ID (e.g. primary residence, rental investment).\nSite-specific configuration.\n"
          },
          "motif_achat": {
            "type": [
              "string",
              "null"
            ],
            "description": "Purchase motivation (validated against referential list 35)."
          },
          "timeframe": {
            "type": [
              "string",
              "null"
            ],
            "description": "Purchase timeframe (validated against referential list 4)."
          },
          "keyword": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free-text keyword for the search."
          },
          "comment": {
            "type": [
              "string",
              "null"
            ],
            "description": "Internal comment on the search request."
          },
          "criteria": {
            "type": [
              "array",
              "null"
            ],
            "description": "Array of search criteria. Each criterion has an `id`, an `operator`, and a `value`.\n\n**Operators:**\n| Operator | Description | Example use |\n|----------|-------------|-------------|\n| `EGAL` | Equals (exact match) | Transaction type, property category |\n| `SUPERIEUR` | Greater than or equal | Min surface, min rooms |\n| `INFERIEUR` | Less than or equal | Max price, max floor |\n| `CONTIENT` | Contains (for multi-value) | Zones, cities |\n| `DIFFERENT` | Not equal | Exclude a type |\n\nUse `GET /criterias/search-requests` to discover available criteria IDs for your site.\n",
            "items": {
              "type": "object",
              "required": [
                "id",
                "operator",
                "value"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Criteria ID (numeric string or XML key).\nUse `GET /criterias/search-requests` to list available IDs.\n",
                  "example": "27"
                },
                "operator": {
                  "type": "string",
                  "enum": [
                    "CONTIENT",
                    "SUPERIEUR",
                    "INFERIEUR",
                    "EGAL",
                    "DIFFERENT"
                  ],
                  "description": "Comparison operator.",
                  "example": "SUPERIEUR"
                },
                "value": {
                  "description": "Value to compare. Type depends on criteria:\n- NUMBER: numeric string (e.g. `\"3\"`, `\"55\"`)\n- TEXT: string\n- UNIQUE/FLAG: value code\nFor zones, multiple values can be comma-separated.\n",
                  "example": "2"
                }
              }
            },
            "example": [
              {
                "id": "TypeBien",
                "operator": "EGAL",
                "value": "Appartement"
              },
              {
                "id": "NbPieces",
                "operator": "SUPERIEUR",
                "value": "2"
              },
              {
                "id": "Prix",
                "operator": "INFERIEUR",
                "value": "300000"
              }
            ]
          },
          "polygons": {
            "type": [
              "array",
              "null"
            ],
            "description": "Geographic polygon filters. Each polygon defines a search zone\nas an array of coordinate points.\n",
            "items": {
              "type": "object",
              "properties": {
                "polygon": {
                  "type": "array",
                  "description": "Array of coordinate points defining the polygon boundary.",
                  "items": {
                    "type": "object",
                    "properties": {
                      "lat": {
                        "type": "number"
                      },
                      "lng": {
                        "type": "number"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "CreateBuyerLeadRequest": {
        "type": "object",
        "required": [
          "agency_id",
          "customer"
        ],
        "properties": {
          "agency_id": {
            "type": "integer",
            "description": "Target agency ID. Must be within the authenticated site's scope.\n",
            "example": 123
          },
          "user_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Assigned user (agent) ID. When omitted, auto-assigned via the configured\nlead distribution rule.\n",
            "example": 456
          },
          "check_duplicate": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Same duplicate behavior as seller leads:\n- `false`/omitted: silently updates existing duplicate\n- `true`: returns 409 on duplicate\n"
          },
          "customer": {
            "$ref": "#/components/schemas/LeadCustomer"
          },
          "search_request": {
            "type": [
              "array",
              "null"
            ],
            "description": "One or more search requests to create for the buyer. Each defines\nthe buyer's search criteria (what they are looking for).\n",
            "items": {
              "$ref": "#/components/schemas/LeadSearchRequest"
            }
          },
          "product_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Existing product ID to follow. When provided, the buyer is linked to\nthis product via a follow-up action. Optional.\n",
            "example": 12345
          },
          "comment": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free-text comment attached to the follow-up action (only used when\n`product_id` is provided).\n",
            "example": "Client très intéressé, rappeler rapidement"
          }
        }
      },
      "SearchTransactionRequest": {
        "type": "object",
        "description": "Search filters for sale transactions. All fields are optional.\nWhen no filters are provided, returns all transactions in scope.\n",
        "properties": {
          "keyword": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free text search across customer names, product model, dossier number.\n",
            "example": "Dupont"
          },
          "is_without_product": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "When true, returns only transactions without a linked product."
          },
          "offer_status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "refusee",
              "attente",
              "annulee",
              "acceptee",
              null
            ],
            "description": "Filter by offer status. Only applicable for sales-offers search.\n- `attente` — Pending\n- `acceptee` — Accepted\n- `refusee` — Refused\n- `annulee` — Cancelled\n"
          },
          "product_model": {
            "type": [
              "string",
              "null"
            ],
            "description": "Filter by product reference (exact or partial match).",
            "example": "REF-2025-001"
          },
          "seller_firstname": {
            "type": [
              "string",
              "null"
            ],
            "description": "Filter by seller first name (partial match)."
          },
          "seller_lastname": {
            "type": [
              "string",
              "null"
            ],
            "description": "Filter by seller last name (partial match)."
          },
          "seller_email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Filter by seller email."
          },
          "buyer_firstname": {
            "type": [
              "string",
              "null"
            ],
            "description": "Filter by buyer first name (partial match)."
          },
          "buyer_lastname": {
            "type": [
              "string",
              "null"
            ],
            "description": "Filter by buyer last name (partial match)."
          },
          "buyer_email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Filter by buyer email."
          },
          "agency_ids": {
            "type": [
              "array",
              "null"
            ],
            "description": "Filter by agency IDs. Each ID must be within the authenticated site's scope.\n",
            "items": {
              "type": "integer"
            },
            "example": [
              123,
              456
            ]
          },
          "user_ids": {
            "type": [
              "array",
              "null"
            ],
            "description": "Filter by assigned user (agent) IDs.",
            "items": {
              "type": "integer"
            },
            "example": [
              789
            ]
          },
          "last_modified": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Only return transactions modified after this date.\nFormat: ISO 8601 with timezone offset (`YYYY-MM-DDTHH:MM:SS+HH:MM`).\n",
            "example": "2025-01-01T00:00:00+01:00"
          },
          "last_created": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Only return transactions created after this date.\nFormat: ISO 8601 with timezone offset.\n",
            "example": "2025-06-01T00:00:00+02:00"
          },
          "is_only_ids": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "When true, returns only transaction IDs (lighter response)."
          },
          "per_page": {
            "type": "integer",
            "minimum": 1,
            "maximum": 200,
            "default": 25,
            "description": "Number of results per page. Maximum 200.\n",
            "example": 50
          },
          "cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Opaque pagination cursor. Pass the `meta.next_cursor` value from the\nprevious response to fetch the next page. Omit for the first page.\n"
          },
          "sort_type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "id",
              "created_at",
              "updated_at",
              null
            ],
            "description": "Field used to sort results."
          },
          "sort_order": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "asc",
              "desc",
              null
            ],
            "description": "Sort direction.",
            "default": "desc"
          },
          "include": {
            "type": [
              "string",
              "null"
            ],
            "description": "Comma-separated relations to eagerly load in the response.\nSee endpoint description for available includes.\n",
            "example": "buyer,seller,fees"
          }
        }
      },
      "SaleTransactionResponse": {
        "type": "object",
        "description": "A sale transaction (compromis, offer, or lease). Fields availability depends\non the transaction type and the `include` parameter.\n",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier of the transaction.",
            "example": 5678
          },
          "status": {
            "type": "object",
            "description": "Current status of the transaction.",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Status ID (from referential list 7)."
              },
              "label": {
                "type": "string",
                "description": "Localized status label.",
                "example": "Actée"
              }
            }
          },
          "offer_status": {
            "type": [
              "string",
              "null"
            ],
            "description": "Current state of the offer (for sales-offers only):\n- `attente` — Pending\n- `acceptee` — Accepted\n- `refusee` — Refused\n- `annulee` — Cancelled\n- `expiree` — Expired\n",
            "enum": [
              "attente",
              "acceptee",
              "refusee",
              "annulee",
              "expiree",
              null
            ]
          },
          "number": {
            "type": [
              "string",
              "null"
            ],
            "description": "Agreement dossier number.",
            "example": "COMP-2025-001"
          },
          "transaction_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "Type of transaction (`Vente` or `Location`).",
            "example": "Vente"
          },
          "product_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "ID of the linked product (property listing).",
            "example": 12345
          },
          "customer_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "ID of the buyer/tenant contact.",
            "example": 789
          },
          "seller_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "ID of the seller/landlord contact.",
            "example": 790
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Transaction creation date.",
            "example": "2025-03-01T10:00:00+01:00"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Last modification date.",
            "example": "2025-06-01T14:30:00+02:00"
          },
          "date_agreement": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Compromise signature date (date SSP — Signature Sous Seing Privé).\n",
            "example": "2025-03-15T00:00:00+01:00"
          },
          "date_act": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Deed signing date (date de signature de l'acte authentique).\nThis is the **effective sale date** used for reporting.\n",
            "example": "2025-06-01T00:00:00+02:00"
          },
          "date_cancellation": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Cancellation date, when applicable."
          },
          "buyer_price": {
            "type": [
              "number",
              "null"
            ],
            "format": "float",
            "description": "Actual buyer price (property price + agency fees).\nThis is the total amount paid by the buyer.\n",
            "example": 355000
          },
          "product_price": {
            "type": [
              "number",
              "null"
            ],
            "format": "float",
            "description": "Property net price (seller price, excluding agency fees).\n",
            "example": 350000
          },
          "agencies": {
            "type": "object",
            "description": "Agency details. Loaded with `include=agencies`."
          },
          "buyer": {
            "type": "object",
            "description": "Buyer contact details. Loaded with `include=buyer`."
          },
          "seller": {
            "type": "object",
            "description": "Seller contact details. Loaded with `include=seller`."
          },
          "fees": {
            "type": [
              "array",
              "null"
            ],
            "description": "Fee breakdown. Loaded with `include=fees`.",
            "items": {
              "type": "object"
            }
          },
          "followed_nego": {
            "type": "object",
            "description": "Agent following the transaction. Loaded with `include=followed_nego`."
          }
        }
      },
      "CursorPaginationMeta-3": {
        "type": "object",
        "description": "Pagination metadata for cursor-based listings.",
        "properties": {
          "per_page": {
            "type": "integer",
            "description": "Number of items returned per page.",
            "example": 25
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor to pass in the next request to fetch the next page.\nNull when there is no next page (last page reached).\n",
            "example": "eyJpZCI6MTIzfQ=="
          },
          "prev_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor to fetch the previous page. Null when on the first page."
          }
        }
      },
      "CursorPaginationLinks-3": {
        "type": "object",
        "description": "Ready-to-use links for cursor-based navigation.",
        "properties": {
          "next": {
            "type": [
              "string",
              "null"
            ],
            "description": "URL of the next page, or null when there is none."
          },
          "prev": {
            "type": [
              "string",
              "null"
            ],
            "description": "URL of the previous page, or null when there is none."
          }
        }
      },
      "UpdateTransactionRequest": {
        "type": "object",
        "description": "Only billing/invoicing fields are updatable via this endpoint.\nSale price (`buyer_price`, `product_price`) and dates (`date_act`, `date_agreement`)\nare **read-only** in the Site API context.\n",
        "properties": {
          "conformity": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 2,
            "description": "Conformity status:\n- `0` — Not verified\n- `1` — Conform\n- `2` — Non-conform\n"
          },
          "c_discount_seller": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Commercial discount amount for seller (in cents or currency unit)."
          },
          "c_discount_buyer": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Commercial discount amount for buyer."
          },
          "no_facture_buyer": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Buyer invoice number (legacy numeric field)."
          },
          "no_facture_seller": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Seller invoice number (legacy numeric field)."
          },
          "num_bill_buyer": {
            "type": [
              "string",
              "null"
            ],
            "description": "Buyer invoice reference (free-text).",
            "example": "FA-2025-001"
          },
          "num_credit_note_buyer": {
            "type": [
              "string",
              "null"
            ],
            "description": "Buyer credit note reference."
          },
          "num_bill_seller": {
            "type": [
              "string",
              "null"
            ],
            "description": "Seller invoice reference.",
            "example": "FA-2025-002"
          },
          "num_credit_note_seller": {
            "type": [
              "string",
              "null"
            ],
            "description": "Seller credit note reference."
          },
          "billing_status": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Billing workflow status. Values depend on the site configuration.\n"
          },
          "payment_amount": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Total payment amount received (in currency unit).",
            "example": 15000
          },
          "amount_recovered": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Amount already recovered/collected."
          },
          "payment_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Date when payment was received.\nFormat: ISO 8601 with timezone offset (`YYYY-MM-DDTHH:MM:SS+HH:MM`).\n",
            "example": "2025-06-01T00:00:00+02:00"
          },
          "date_bill_buyer": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Buyer invoice date (format `YYYY-MM-DD`).",
            "example": "2025-06-05"
          },
          "date_credit_note_buyer": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Buyer credit note date."
          },
          "date_bill_seller": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Seller invoice date.",
            "example": "2025-06-05"
          },
          "date_credit_note_seller": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Seller credit note date."
          },
          "fees": {
            "type": [
              "array",
              "null"
            ],
            "description": "Fee percentage updates. Each entry must reference an existing fee `id`\nfrom the transaction's fee records.\n",
            "items": {
              "type": "object",
              "required": [
                "id",
                "percentage"
              ],
              "properties": {
                "id": {
                  "type": "integer",
                  "description": "ID of the existing fee record to update."
                },
                "percentage": {
                  "type": "number",
                  "format": "float",
                  "minimum": 0,
                  "maximum": 100,
                  "description": "Fee percentage (0 to 100).",
                  "example": 5
                }
              }
            }
          }
        }
      },
      "UserResponse": {
        "type": "object",
        "description": "User (agent/collaborator) details. Some fields are only populated when\nthe user has data for them; null otherwise.\n",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique user identifier.",
            "example": 582602
          },
          "firstname": {
            "type": [
              "string",
              "null"
            ],
            "example": "Marie"
          },
          "lastname": {
            "type": [
              "string",
              "null"
            ],
            "example": "Martin"
          },
          "gender": {
            "type": [
              "string",
              "null"
            ],
            "description": "Gender code (m, f, s, etc.).",
            "example": "f"
          },
          "sexe": {
            "type": [
              "string",
              "null"
            ],
            "description": "Sexe label (legacy field)."
          },
          "is_rsac_displayed": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the RSAC number is displayed publicly."
          },
          "ai_consent": {
            "type": "boolean",
            "description": "Whether the user consented to AI features."
          },
          "editor_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Network/editor code this user belongs to."
          },
          "picture": {
            "type": [
              "string",
              "null"
            ],
            "description": "Profile picture URL."
          },
          "signature": {
            "type": [
              "string",
              "null"
            ],
            "description": "Signature picture URL."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "example": "marie.martin@agence.fr"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Office phone number.",
            "example": "+33498765432"
          },
          "mobile_phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Mobile phone number.",
            "example": "+33698765432"
          },
          "site_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Primary site code."
          },
          "rsac_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "RSAC registration number (commercial agent)."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Public bio/description."
          },
          "delegation": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether delegation is enabled."
          },
          "status": {
            "type": [
              "integer",
              "null"
            ],
            "description": "1=Active, 0=Archived, 3=Hidden/Deleted",
            "example": 1
          },
          "rsac_city": {
            "type": [
              "string",
              "null"
            ],
            "description": "RSAC registration city."
          },
          "production_ratio": {
            "type": [
              "number",
              "null"
            ],
            "description": "Production ratio (performance metric)."
          },
          "ip_filter": {
            "type": [
              "string",
              "null"
            ],
            "description": "IP address whitelist for this user."
          },
          "group_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Primary permission group ID.",
            "example": 2
          },
          "group_id2": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Secondary permission group ID."
          },
          "last_login_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Last login timestamp.",
            "example": "2026-06-15T08:30:00+02:00"
          },
          "is_email_valid": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the email has been validated."
          },
          "color": {
            "type": [
              "string",
              "null"
            ],
            "description": "Calendar color code (hex)."
          },
          "experience": {
            "type": [
              "string",
              "null"
            ],
            "description": "Years of experience or experience description."
          },
          "career_path": {
            "type": [
              "string",
              "null"
            ]
          },
          "specialty": {
            "type": [
              "string",
              "null"
            ],
            "description": "Professional specialty."
          },
          "zimbra_login": {
            "type": [
              "string",
              "null"
            ],
            "description": "Zimbra email login."
          },
          "zimbra_alias": {
            "type": [
              "string",
              "null"
            ],
            "description": "Zimbra email alias."
          },
          "agency_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Primary agency ID.",
            "example": 123
          },
          "agencies_list": {
            "type": [
              "string",
              "null"
            ],
            "description": "Comma-separated list of accessible agency IDs.",
            "example": "123,456"
          },
          "agencies_list_affectation": {
            "type": [
              "string",
              "null"
            ],
            "description": "Comma-separated list of lead affectation agency IDs."
          },
          "is_visible_directory": {
            "type": "boolean",
            "description": "Whether the user is visible in the public agency directory."
          },
          "professional_liability_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "Professional liability insurance number."
          },
          "professional_liability_organization": {
            "type": [
              "string",
              "null"
            ],
            "description": "Insurance organization name."
          },
          "professional_liability_address": {
            "type": [
              "string",
              "null"
            ],
            "description": "Insurance organization address."
          },
          "jestimo_login": {
            "type": [
              "string",
              "null"
            ],
            "description": "Jestimo (valuation tool) login."
          },
          "jestimo_password": {
            "type": [
              "string",
              "null"
            ],
            "description": "Jestimo password."
          },
          "user_types": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "List of user type IDs (professional roles).",
            "example": [
              1
            ]
          },
          "logs": {
            "type": [
              "array",
              "null"
            ],
            "description": "Recent action log entries for this user.\nLoaded only when `?include=logs` is requested.\n",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Action type label."
                },
                "date_performed": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                }
              }
            }
          }
        }
      },
      "CreateUserRequest": {
        "type": "object",
        "required": [
          "firstname",
          "lastname",
          "email",
          "status",
          "agency_id",
          "password",
          "group_id"
        ],
        "description": "Request body for creating a new user.",
        "properties": {
          "firstname": {
            "type": "string",
            "description": "User first name.",
            "example": "Marie"
          },
          "lastname": {
            "type": "string",
            "description": "User last name.",
            "example": "Martin"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "User email address. Must be unique across the site.\nUsed for login and notifications.\n",
            "example": "marie.martin@agence.fr"
          },
          "password": {
            "type": "string",
            "description": "User password. Must meet RGPD requirements:\n- Length: 12-40 characters\n- Must contain: uppercase, lowercase, digit, special character\n",
            "example": "S3cur3P@ss!2026"
          },
          "status": {
            "type": "integer",
            "description": "User status:\n- `1` — Active\n- `0` — Archived (cannot login)\n",
            "example": 1
          },
          "agency_id": {
            "type": "integer",
            "description": "Primary agency ID for the user. Must be within the authenticated site's scope.\n",
            "example": 123
          },
          "agencies_list": {
            "type": [
              "array",
              "null"
            ],
            "description": "Additional agency IDs the user has access to (multi-agency access).\nEach must be within the site scope.\n",
            "items": {
              "type": "integer"
            },
            "example": [
              123,
              456
            ]
          },
          "group_id": {
            "type": "integer",
            "description": "Permission group ID. Determines access level.\nUse `GET /users/groups` to discover available groups.\n",
            "example": 2
          },
          "delegation": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the user can delegate their work to others."
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Office phone number.",
            "example": "+33498765432"
          },
          "mobile_phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Mobile phone number.",
            "example": "+33698765432"
          },
          "gender": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "m",
              "f",
              "s"
            ],
            "description": "Gender code. Use `GET /users/genders` for the full list.\n",
            "example": "f"
          },
          "user_types": {
            "type": [
              "array",
              "null"
            ],
            "description": "User type IDs (professional roles). A user can have multiple types.\nUse `GET /users/types` to discover available types.\n",
            "items": {
              "type": "integer"
            },
            "example": [
              1
            ]
          }
        }
      },
      "UpdateUserRequest": {
        "type": "object",
        "description": "Partial user update. Only provided fields are modified.\n",
        "properties": {
          "firstname": {
            "type": [
              "string",
              "null"
            ]
          },
          "lastname": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "password": {
            "type": [
              "string",
              "null"
            ],
            "description": "New password (same RGPD requirements as create)."
          },
          "status": {
            "type": [
              "integer",
              "null"
            ],
            "description": "1 = Active, 0 = Archived"
          },
          "agency_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Change primary agency."
          },
          "agencies_list": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "integer"
            }
          },
          "group_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "delegation": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "mobile_phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "gender": {
            "type": [
              "string",
              "null"
            ]
          },
          "user_types": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "integer"
            }
          }
        }
      },
      "UserGroupResponse": {
        "type": "object",
        "description": "A user permission group.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Group ID.",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Group display name.",
            "example": "Administrateur"
          },
          "delegation": {
            "type": "boolean",
            "description": "Whether users in this group can delegate work."
          }
        }
      },
      "UserTypeResponse": {
        "type": "object",
        "description": "A professional role type.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Type ID.",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Type name (masculine).",
            "example": "Négociateur"
          },
          "name_female": {
            "type": [
              "string",
              "null"
            ],
            "description": "Type name (feminine form).",
            "example": "Négociatrice"
          }
        }
      },
      "GenderResponse": {
        "type": "object",
        "description": "A gender option.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Gender code (used in create/update requests).",
            "example": "m"
          },
          "description": {
            "type": "string",
            "description": "Full label.",
            "example": "Monsieur"
          },
          "description_legal": {
            "type": [
              "string",
              "null"
            ],
            "description": "Legal form description."
          },
          "abbreviation": {
            "type": [
              "string",
              "null"
            ],
            "description": "Short abbreviation.",
            "example": "M."
          },
          "sexe": {
            "type": [
              "string",
              "null"
            ],
            "description": "Sexe indicator."
          },
          "is_entity": {
            "type": "boolean",
            "description": "Whether this gender represents a legal entity (company/société).\nWhen true, the contact is a professional/company, not a person.\n"
          },
          "private": {
            "type": "boolean",
            "description": "Whether this gender is for internal use only."
          }
        }
      },
      "WebhookResponse": {
        "type": "object",
        "description": "A registered webhook subscription.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique webhook identifier.",
            "example": 42
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Target URL that receives webhook payloads via HTTP POST.",
            "example": "https://portail.example.com/webhook"
          },
          "headers": {
            "type": [
              "object",
              "null"
            ],
            "description": "Custom headers sent with each webhook payload request.\nUse this to authenticate incoming webhook requests on your server.\n",
            "example": {
              "X-Api-Key": "secret"
            }
          },
          "authId": {
            "type": [
              "integer",
              "null"
            ],
            "description": "ID of the linked webhook auth configuration (if any)."
          },
          "format": {
            "type": [
              "string",
              "null"
            ],
            "description": "Custom format identifier for the payload (if any)."
          },
          "origines": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "List of subscribed event types. Only events in this list trigger\na delivery to the target URL.\n",
            "example": [
              "CUSTOMER_CREATE",
              "PRODUCT_UPDATE"
            ]
          }
        }
      },
      "CreateWebhookRequest": {
        "type": "object",
        "required": [
          "url",
          "origines"
        ],
        "description": "Request body for creating a new webhook subscription.",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "HTTPS URL that will receive webhook payloads via POST.\nMust be publicly accessible and respond within 10 seconds.\n",
            "example": "https://portail.example.com/webhook"
          },
          "origines": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Event types to subscribe to. Each value is validated against the list\nof available hook origins for the site. Invalid values return 422.\n",
            "example": [
              "CUSTOMER_CREATE",
              "CUSTOMER_UPDATE",
              "PRODUCT_CREATE",
              "PRODUCT_UPDATE"
            ]
          },
          "headers": {
            "type": [
              "object",
              "null"
            ],
            "description": "Custom key-value headers to include in every webhook HTTP request.\nUseful for authentication (e.g. API keys).\n",
            "example": {
              "X-Api-Key": "secret"
            }
          },
          "auth_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "ID of a webhook auth configuration to use for request authentication."
          },
          "format": {
            "type": [
              "string",
              "null"
            ],
            "description": "Custom format identifier for the payload structure."
          }
        }
      },
      "UpdateWebhookRequest": {
        "type": "object",
        "description": "Update fields for a webhook. All fields are optional — only provided fields\nare modified.\n",
        "properties": {
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "New target URL.",
            "example": "https://new-portail.example.com/webhook"
          },
          "origines": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "Updated list of event types (replaces the current list entirely).",
            "example": [
              "CUSTOMER_CREATE",
              "PRODUCT_UPDATE",
              "PRODUCT_DELETE"
            ]
          },
          "headers": {
            "type": [
              "object",
              "null"
            ],
            "description": "Updated custom headers (replaces entirely).",
            "example": {
              "X-Api-Key": "new-secret"
            }
          },
          "auth_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Updated webhook auth configuration ID."
          },
          "format": {
            "type": [
              "string",
              "null"
            ],
            "description": "Updated format identifier."
          }
        }
      },
      "ListeValue": {
        "type": "object",
        "description": "A single value entry in a referential list.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Value ID (used in API requests to reference this value).",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Display label.",
            "example": "Chambre"
          },
          "order": {
            "type": "integer",
            "description": "Sort order for display.",
            "example": 1
          }
        }
      },
      "Liste": {
        "type": "object",
        "description": "A referential list with its values.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "List ID.",
            "example": 1
          },
          "values": {
            "type": "array",
            "description": "Available values in this list.",
            "items": {
              "$ref": "#/components/schemas/ListeValue"
            }
          }
        }
      },
      "EventTypeResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique identifier of the event type, used as the `type` value when creating events",
            "example": "12"
          },
          "key": {
            "type": "string",
            "description": "Stable key of the event type.",
            "example": "VISIT"
          },
          "label": {
            "type": "string",
            "description": "Display name of the event type.",
            "example": "Visite"
          },
          "color": {
            "type": [
              "string",
              "null"
            ],
            "description": "Hexadecimal color used to display events of this type.",
            "example": "#5B2D8E"
          },
          "booking_behavior": {
            "type": [
              "string",
              "null"
            ],
            "description": "Indicates how online appointment booking behaves for this event type."
          },
          "displayed": {
            "type": "boolean",
            "description": "Whether this event type is shown in the agency interface."
          },
          "event_form": {
            "type": "object",
            "description": "Form layout for this event type. Returned only when form information is available.\n",
            "properties": {
              "fields": {
                "type": "array",
                "description": "Keys of the fields displayed on the event form.",
                "items": {
                  "type": "string"
                }
              },
              "collapsed_fields": {
                "type": "array",
                "description": "Keys of the fields hidden by default (shown only when the form is expanded).",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "CursorPaginationMeta-4": {
        "type": "object",
        "description": "Pagination metadata for cursor-based listings.",
        "properties": {
          "per_page": {
            "type": "integer",
            "description": "Number of items returned per page.",
            "example": 50
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor to pass as `cursor` to fetch the next page. Null when there is no next page.",
            "example": "eyJpZCI6MTIzfQ=="
          },
          "prev_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor to fetch the previous page. Null when there is no previous page."
          }
        }
      },
      "HourMinute": {
        "type": "object",
        "description": "A time of day expressed as hours and minutes.",
        "properties": {
          "hours": {
            "type": "integer",
            "minimum": 0,
            "maximum": 23,
            "description": "Hour of the day (0-23)."
          },
          "minutes": {
            "type": "integer",
            "minimum": 0,
            "maximum": 59,
            "description": "Minutes (0-59)."
          }
        }
      },
      "DayTimeSlot": {
        "type": "object",
        "description": "A time range within a day.",
        "properties": {
          "start": {
            "$ref": "#/components/schemas/HourMinute"
          },
          "end": {
            "$ref": "#/components/schemas/HourMinute"
          }
        }
      },
      "PermanenceTimeframeResponse": {
        "type": "object",
        "description": "Client reception (permanence) slots, grouped by day of week\n(`MO`, `TU`, `WE`, `TH`, `FR`, `SA`, `SU`). Each day holds a list of time ranges.\n",
        "additionalProperties": {
          "type": "array",
          "items": {
            "$ref": "#/components/schemas/DayTimeSlot"
          }
        },
        "example": {
          "MO": [
            {
              "start": {
                "hours": 9,
                "minutes": 0
              },
              "end": {
                "hours": 12,
                "minutes": 0
              }
            }
          ],
          "TU": []
        }
      },
      "PermanenceUserResponse": {
        "type": "object",
        "properties": {
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "Start of the permanence slot, formatted in the requested timezone.",
            "example": "2026-01-15T09:00:00+01:00"
          },
          "end": {
            "type": "string",
            "format": "date-time",
            "description": "End of the permanence slot, formatted in the requested timezone.",
            "example": "2026-01-15T12:00:00+01:00"
          },
          "user_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Identifier of the user assigned to the slot."
          }
        }
      },
      "WorkingTimeResponse": {
        "type": "object",
        "description": "Working time slots grouped by day of week. Each day key (`MO`, `TU`, `WE`, `TH`,\n`FR`, `SA`, `SU`) holds the list of time ranges worked that day.\n",
        "properties": {
          "MO": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DayTimeSlot"
            }
          },
          "TU": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DayTimeSlot"
            }
          },
          "WE": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DayTimeSlot"
            }
          },
          "TH": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DayTimeSlot"
            }
          },
          "FR": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DayTimeSlot"
            }
          },
          "SA": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DayTimeSlot"
            }
          },
          "SU": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DayTimeSlot"
            }
          }
        }
      },
      "AvailabilitySlot": {
        "type": "object",
        "description": "A free slot during which the user is available, derived from their working\ntime minus their existing events.\n",
        "properties": {
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "Start of the available slot, formatted in the requested timezone."
          },
          "end": {
            "type": "string",
            "format": "date-time",
            "description": "End of the available slot, formatted in the requested timezone."
          }
        }
      },
      "SharedAgendaResponse": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": "string",
            "description": "Identifier of the user whose agenda is accessible.",
            "example": "42"
          },
          "firstname": {
            "type": [
              "string",
              "null"
            ],
            "description": "First name of the user."
          },
          "lastname": {
            "type": [
              "string",
              "null"
            ],
            "description": "Last name of the user."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "Email address of the user."
          },
          "status": {
            "type": "object",
            "description": "Visibility status of the user's agenda.",
            "properties": {
              "is_archived": {
                "type": "boolean",
                "description": "True when the user is archived."
              },
              "is_hidden": {
                "type": "boolean",
                "description": "True when the user is hidden (archived or deleted)."
              }
            }
          }
        }
      },
      "EventParticipantResponse": {
        "type": "object",
        "properties": {
          "ext_user_id": {
            "type": "integer",
            "description": "Identifier of the participating user (agent)."
          },
          "is_creator": {
            "type": "boolean",
            "description": "True for the participant who created the event."
          },
          "is_organizer": {
            "type": "boolean",
            "description": "True for the participant who owns/hosts the event."
          }
        }
      },
      "EventResponse": {
        "type": "object",
        "description": "A calendar event. The set of populated fields depends on the event type and\non the information supplied when the event was created.\n",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier of the event."
          },
          "type": {
            "type": "string",
            "description": "Event type key (matches the `key` of an entry from the event types list).",
            "example": "VISIT"
          },
          "starts_at": {
            "type": "string",
            "description": "Event start, UTC datetime (`YYYY-MM-DDTHH:MM:SSZ`).",
            "example": "2026-01-15T09:00:00Z"
          },
          "ends_at": {
            "type": "string",
            "description": "Event end, UTC datetime (`YYYY-MM-DDTHH:MM:SSZ`).",
            "example": "2026-01-15T10:00:00Z"
          },
          "all_day": {
            "type": "boolean",
            "description": "True when the event lasts the whole day and the time portion is not significant."
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "description": "Title displayed on the event."
          },
          "location": {
            "type": [
              "string",
              "null"
            ],
            "description": "Address or place where the event takes place."
          },
          "location_geo": {
            "type": [
              "object",
              "null"
            ],
            "description": "Geographic coordinates of the location, when known.",
            "properties": {
              "lat": {
                "type": "number",
                "description": "Latitude in decimal degrees.",
                "example": 48.8566
              },
              "lng": {
                "type": "number",
                "description": "Longitude in decimal degrees.",
                "example": 2.3522
              }
            }
          },
          "sequence_duration": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Duration of a single booking slot, in minutes."
          },
          "gathering_gauge": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Maximum number of attendees that can book the same slot."
          },
          "commute_duration": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Travel time reserved before the event, in minutes."
          },
          "communicate_location": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the location may be shared with attendees."
          },
          "is_confidential": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "When true, the event details are hidden from non-participants; the slot still shows as busy."
          },
          "remind_time_in_min": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Reminder lead time before the event start, in minutes."
          },
          "appointment_mode": {
            "type": [
              "string",
              "null"
            ],
            "description": "How the appointment is conducted:\n- `OFFICE`: at the agency office\n- `PHONE`: by phone\n- `PRODUCT`: on-site at the property\n- `VISIO`: video conference\n",
            "enum": [
              "OFFICE",
              "PHONE",
              "PRODUCT",
              "VISIO",
              null
            ]
          },
          "participants": {
            "type": "array",
            "description": "Users (agents) attending the event.",
            "items": {
              "$ref": "#/components/schemas/EventParticipantResponse"
            }
          },
          "products": {
            "type": "array",
            "description": "Property listings linked to the event.",
            "items": {
              "type": "object",
              "properties": {
                "ext_product_id": {
                  "type": "string",
                  "description": "Identifier of the linked property listing."
                },
                "ext_domain_id": {
                  "type": "string",
                  "description": "Identifier of the domain the listing belongs to."
                }
              }
            }
          },
          "contacts": {
            "type": "array",
            "description": "Contacts (customers) linked to the event.",
            "items": {
              "type": "object",
              "properties": {
                "ext_contact_id": {
                  "type": "string",
                  "description": "Identifier of the linked contact."
                },
                "ext_domain_id": {
                  "type": "string",
                  "description": "Identifier of the domain the contact belongs to."
                }
              }
            }
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free-text notes attached to the event."
          },
          "visio_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Link to the video-conference room (used when `appointment_mode` is `VISIO`)."
          },
          "immo360_organizer_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Link used by the agent to host the live/virtual visit."
          },
          "immo360_participation_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Link shared with attendees to join the live/virtual visit."
          },
          "archived": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "True when the event has been archived (hidden from default listings but restorable)."
          },
          "parent_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Identifier of the parent event when this entry is an occurrence of a recurring series."
          },
          "recurrence": {
            "type": [
              "object",
              "null"
            ],
            "description": "Recurrence definition of the event, when it is part of a recurring series."
          }
        }
      },
      "EventParticipantInput": {
        "type": "object",
        "required": [
          "user_id",
          "is_creator",
          "is_organizer"
        ],
        "properties": {
          "user_id": {
            "type": "integer",
            "description": "Identifier of the participating user (agent). Must be unique within the\nparticipants list.\n"
          },
          "is_creator": {
            "type": "boolean",
            "description": "Marks the participant who created the event. Exactly one participant in the\nlist must have this set to true.\n"
          },
          "is_organizer": {
            "type": "boolean",
            "description": "Marks the participant who owns/hosts the event (its primary agenda).\nExactly one participant in the list must have this set to true.\n"
          }
        }
      },
      "EventRecurrence": {
        "type": [
          "object",
          "null"
        ],
        "description": "iCalendar RRULE-style recurrence definition. When present, `dtstart`, `freq`\nand `interval` are required. The `by*` arrays follow the\n[RFC 5545](https://datatracker.ietf.org/doc/html/rfc5545) recurrence semantics.\n",
        "required": [
          "dtstart",
          "freq",
          "interval"
        ],
        "properties": {
          "dtstart": {
            "type": "string",
            "description": "First occurrence start, UTC datetime in format `YYYY-MM-DDTHH:MM:SSZ`.\n",
            "example": "2026-01-15T09:00:00Z"
          },
          "freq": {
            "type": "string",
            "description": "Recurrence frequency.",
            "enum": [
              "yearly",
              "monthly",
              "weekly",
              "daily",
              "hourly",
              "minutely",
              "secondly"
            ]
          },
          "interval": {
            "type": "integer",
            "minimum": 1,
            "description": "Interval between occurrences in units of `freq`\n(e.g. `freq: weekly`, `interval: 2` = every two weeks).\n"
          },
          "count": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "description": "Total number of occurrences to generate. Mutually exclusive with `until`.\n"
          },
          "until": {
            "type": [
              "string",
              "null"
            ],
            "description": "End date of the recurrence (inclusive), UTC `YYYY-MM-DDTHH:MM:SSZ`.\nMutually exclusive with `count`.\n",
            "example": "2026-12-31T23:59:59Z"
          },
          "bymonth": {
            "type": [
              "array",
              "null"
            ],
            "description": "Months of the year the event applies to (1 = January … 12 = December).",
            "items": {
              "type": "integer",
              "minimum": 1,
              "maximum": 12
            }
          },
          "bymonthday": {
            "type": [
              "array",
              "null"
            ],
            "description": "Days of the month (1..31, or negative to count from the end of the month,\ne.g. -1 = last day).\n",
            "items": {
              "type": "integer",
              "minimum": -31,
              "maximum": 31
            }
          },
          "bynmonthday": {
            "type": [
              "array",
              "null"
            ],
            "description": "Negative-indexed days of the month (counted from the end).",
            "items": {
              "type": "integer"
            }
          },
          "byyearday": {
            "type": [
              "array",
              "null"
            ],
            "description": "Days of the year (1..366, or negative from the end of the year).",
            "items": {
              "type": "integer",
              "minimum": -366,
              "maximum": 366
            }
          },
          "byweekno": {
            "type": [
              "array",
              "null"
            ],
            "description": "ISO week numbers of the year (1..53, or negative from the end).",
            "items": {
              "type": "integer",
              "minimum": -53,
              "maximum": 53
            }
          },
          "byweekday": {
            "type": [
              "array",
              "null"
            ],
            "description": "Days of the week the event recurs on, as two-letter codes\n(MO, TU, WE, TH, FR, SA, SU).\n",
            "items": {
              "type": "string",
              "enum": [
                "MO",
                "TU",
                "WE",
                "TH",
                "FR",
                "SA",
                "SU"
              ]
            }
          },
          "bynweekday": {
            "type": [
              "array",
              "null"
            ],
            "description": "Ordinal weekday rules as `[weekday_index, position]` pairs, where\nweekday_index is 0=Monday … 6=Sunday and position is the occurrence within\nthe month (e.g. `[1, 2]` = the second Tuesday). Used for monthly-by-day patterns.\n",
            "items": {
              "type": "array",
              "items": {
                "type": "integer"
              }
            }
          },
          "bysetpos": {
            "type": [
              "array",
              "null"
            ],
            "description": "Selects the Nth occurrence(s) within the set produced by the other `by*`\nrules (e.g. -1 = the last matching day of the period).\n",
            "items": {
              "type": "integer"
            }
          },
          "exdate": {
            "type": [
              "array",
              "null"
            ],
            "description": "Exception dates to exclude from the generated series, each a UTC datetime\n`YYYY-MM-DDTHH:MM:SSZ`.\n",
            "items": {
              "type": "string",
              "example": "2026-02-15T09:00:00Z"
            }
          }
        }
      },
      "CreateEventRequest": {
        "type": "object",
        "required": [
          "type",
          "starts_at",
          "ends_at",
          "all_day"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "Event type identifier as returned by `GET /calendar/events/types` (the `id` field).\nDetermines the behavior and the available form fields for the event.\n",
            "example": "\"7\" = visit"
          },
          "starts_at": {
            "type": "string",
            "description": "Event start, UTC datetime in strict format `YYYY-MM-DDTHH:MM:SSZ`\n(the trailing `Z` is required).\n",
            "example": "2026-01-15T09:00:00Z"
          },
          "ends_at": {
            "type": "string",
            "description": "Event end, UTC datetime in strict format `YYYY-MM-DDTHH:MM:SSZ`\n(the trailing `Z` is required). Must be after `starts_at`.\n",
            "example": "2026-01-15T10:00:00Z"
          },
          "all_day": {
            "type": "boolean",
            "description": "When true, the event spans the whole day(s) and the time portion of\n`starts_at`/`ends_at` is ignored by calendar displays.\n"
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free-text title shown on the event. Falls back to the event type label when null."
          },
          "location": {
            "type": [
              "string",
              "null"
            ],
            "description": "Human-readable address or place where the event takes place."
          },
          "location_geo": {
            "type": [
              "object",
              "null"
            ],
            "description": "Geographic coordinates of the event location. When provided, both `lat`\nand `lng` are required.\n",
            "required": [
              "lat",
              "lng"
            ],
            "properties": {
              "lat": {
                "type": "number",
                "description": "Latitude in decimal degrees.",
                "example": 48.8566
              },
              "lng": {
                "type": "number",
                "description": "Longitude in decimal degrees.",
                "example": 2.3522
              }
            }
          },
          "sequence_duration": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Duration of a single appointment slot in minutes, used when the event type\nallows online booking with successive time slots (e.g. open-house sequences).\n"
          },
          "gathering_gauge": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Maximum number of attendees that can book the same slot (capacity).\nUsed for group/open-house style appointments.\n"
          },
          "commute_duration": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Estimated travel time to the location, in minutes, reserved before the event\nfor the agent's commute.\n"
          },
          "communicate_location": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the event location may be shared with the attendees/contacts."
          },
          "is_confidential": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "When true, event details are hidden from users who are not participants;\nthe slot still appears as busy.\n"
          },
          "remind_time_in_min": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "Lead time of the reminder before the event start, in minutes\n(e.g. 15 = remind 15 minutes before). 0 disables the offset.\n"
          },
          "appointment_mode": {
            "type": [
              "string",
              "null"
            ],
            "description": "How the appointment is conducted:\n- `OFFICE`: at the agency office\n- `PHONE`: by phone\n- `PRODUCT`: on-site at the property\n- `VISIO`: video conference\n",
            "enum": [
              "OFFICE",
              "PHONE",
              "PRODUCT",
              "VISIO",
              null
            ]
          },
          "participants": {
            "type": [
              "array",
              "null"
            ],
            "description": "Users (agents) attending the event. Exactly one must have `is_organizer: true`\nand exactly one `is_creator: true`, and every `user_id` must be unique.\n",
            "items": {
              "$ref": "#/components/schemas/EventParticipantInput"
            }
          },
          "products": {
            "type": [
              "array",
              "null"
            ],
            "description": "Immofacile product (property listing) IDs linked to the event.\nEach ID must be within the client update scope.\n",
            "items": {
              "type": "integer"
            }
          },
          "contacts": {
            "type": [
              "array",
              "null"
            ],
            "description": "Immofacile customer (contact) IDs linked to the event.",
            "items": {
              "type": "integer"
            }
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free-text internal notes attached to the event."
          },
          "visio_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "URL of the video-conference room (used when `appointment_mode` is `VISIO`)."
          },
          "immo360_organizer_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Link used by the agent to host the live/virtual visit."
          },
          "immo360_participation_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Link shared with attendees to join the live/virtual visit."
          },
          "archived": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the event is created in an archived (hidden) state."
          },
          "parent_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "ID of the parent event when this event is an occurrence/child of a\nrecurring series.\n"
          },
          "recurrence": {
            "$ref": "#/components/schemas/EventRecurrence"
          }
        }
      },
      "UpdateEventRequest": {
        "type": "object",
        "description": "Partial update of an event. Every top-level field is optional; only the\nprovided fields are modified. Field meanings are identical to\n`CreateEventRequest` (see that schema for full descriptions).\n",
        "properties": {
          "type": {
            "type": [
              "string",
              "null"
            ]
          },
          "starts_at": {
            "type": [
              "string",
              "null"
            ],
            "example": "2026-01-15T09:00:00Z"
          },
          "ends_at": {
            "type": [
              "string",
              "null"
            ],
            "example": "2026-01-15T10:00:00Z"
          },
          "all_day": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "location": {
            "type": [
              "string",
              "null"
            ]
          },
          "location_geo": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "lat",
              "lng"
            ],
            "properties": {
              "lat": {
                "type": "number"
              },
              "lng": {
                "type": "number"
              }
            }
          },
          "sequence_duration": {
            "type": [
              "integer",
              "null"
            ]
          },
          "gathering_gauge": {
            "type": [
              "integer",
              "null"
            ]
          },
          "commute_duration": {
            "type": [
              "integer",
              "null"
            ]
          },
          "communicate_location": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "is_confidential": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "remind_time_in_min": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "appointment_mode": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "OFFICE",
              "PHONE",
              "PRODUCT",
              "VISIO",
              null
            ]
          },
          "participants": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/EventParticipantInput"
            }
          },
          "products": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "integer"
            }
          },
          "contacts": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "integer"
            }
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "visio_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "immo360_organizer_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "immo360_participation_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "archived": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "parent_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "recurrence": {
            "$ref": "#/components/schemas/EventRecurrence"
          }
        }
      },
      "WebhookEventPayload": {
        "type": "object",
        "required": [
          "event_type",
          "resource_id",
          "timestamp"
        ],
        "description": "Payload delivered by Immofacile when a subscribed event occurs.\n",
        "properties": {
          "event_type": {
            "type": "string",
            "description": "The event that triggered the notification, in `RESOURCE_ACTION` form.\n",
            "example": "CUSTOMER_CREATE"
          },
          "resource_id": {
            "type": "integer",
            "description": "Identifier of the affected resource.",
            "example": 789
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "When the event occurred (RFC 3339).",
            "example": "2026-09-11T14:30:00+02:00"
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Authentication required",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Insufficient permissions",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "ValidationError": {
        "description": "Validation failed",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationErrorResponse"
            }
          }
        }
      }
    }
  }
}