{
  "openapi": "3.1.0",
  "info": {
    "title": "Maideo Agent API",
    "description": "Public HTTP API for autonomous AI agents to book professional home cleaning services (ménage à domicile) in France. Payment is handled via the URSSAF immediate tax credit advance (avance immédiate de crédit d'impôt) — end users pay only 50% of the gross hourly rate via SEPA direct debit, no card required.",
    "version": "1.0.0",
    "contact": {
      "name": "Maideo Agent API support",
      "email": "dev@maideo.fr",
      "url": "https://www.maideo.fr/agents.txt"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://www.maideo.fr/conditions-utilisation"
    }
  },
  "servers": [
    {
      "url": "https://api.maideo.fr/public/agent",
      "description": "Production"
    }
  ],
  "tags": [
    { "name": "discovery", "description": "Check coverage and pricing before booking" },
    { "name": "booking", "description": "Quote, book, and follow up on a booking" },
    { "name": "payment", "description": "URSSAF immediate tax credit advance enrollment" }
  ],
  "components": {
    "securitySchemes": {
      "BookingToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "HMAC-signed JWT returned by POST /book. Scope: booking/:bookingId. TTL: 72h."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "details": { "type": "array", "items": { "type": "string" } }
        },
        "required": ["error"]
      },
      "Address": {
        "type": "object",
        "required": ["street", "city", "zip"],
        "properties": {
          "street": { "type": "string" },
          "city": { "type": "string" },
          "zip": { "type": "string", "pattern": "^[0-9]{5}$" },
          "country": { "type": "string", "default": "France" },
          "lat": { "type": "number" },
          "lon": { "type": "number" },
          "accessInfo": { "type": "string" }
        }
      },
      "PrestationInfo": {
        "type": "object",
        "properties": {
          "houseType": { "type": "string", "enum": ["Maison", "Appartement", "Studio"], "default": "Maison" },
          "houseSize": { "type": "integer", "description": "Number of rooms (chambres + sdb)" },
          "repassage": { "type": "boolean" },
          "pets": {
            "type": "object",
            "properties": {
              "dog": { "type": "boolean" },
              "cat": { "type": "boolean" }
            }
          },
          "comments": { "type": "string" }
        }
      },
      "CoverageResponse": {
        "type": "object",
        "properties": {
          "served": { "type": "boolean" },
          "zip": { "type": "string" },
          "departement": { "type": "string" },
          "prestation": { "type": "string" },
          "activeWorkers": { "type": "integer" },
          "avgStaffingDelayDays": { "type": "integer", "nullable": true },
          "estimatedHourlyRate": { "type": "number", "nullable": true },
          "currency": { "type": "string" },
          "creditImpotRate": { "type": "number" },
          "notice": { "type": "string" }
        }
      },
      "QuoteRequest": {
        "type": "object",
        "required": ["zip", "nbHeuresSemaine", "frequency"],
        "properties": {
          "prestation": { "type": "string", "enum": ["MENAGE"], "default": "MENAGE" },
          "zip": { "type": "string", "pattern": "^[0-9]{5}$" },
          "city": { "type": "string" },
          "nbHeuresSemaine": { "type": "number", "minimum": 2 },
          "nbPrestaSemaine": { "type": "integer", "default": 1 },
          "frequency": { "type": "string", "enum": ["one_shot", "weekly", "bi_weekly", "monthly"] },
          "prestationInfo": { "$ref": "#/components/schemas/PrestationInfo" }
        }
      },
      "QuoteResponse": {
        "type": "object",
        "properties": {
          "quoteId": { "type": "string" },
          "quoteToken": { "type": "string", "description": "Pass this to POST /book. Valid 72h." },
          "expiresInSeconds": { "type": "integer" },
          "quote": {
            "type": "object",
            "properties": {
              "prestation": { "type": "string" },
              "pricePerHour": { "type": "number" },
              "currency": { "type": "string" },
              "nbHeuresSemaine": { "type": "number" },
              "nbPrestaSemaine": { "type": "integer" },
              "frequency": { "type": "string" },
              "monthlyHours": { "type": "number" },
              "weeklyHours": { "type": "number" },
              "weeklyAmount": { "type": "number" },
              "weeklyAmountAfterCreditImpot": { "type": "number" },
              "potentialAmount": { "type": "number" },
              "potentialAmountAfterCreditImpot": { "type": "number" },
              "creditImpotRate": { "type": "number" }
            }
          },
          "notice": { "type": "string" }
        }
      },
      "BookRequest": {
        "type": "object",
        "required": ["quoteToken", "client", "address", "dateDebut", "agentConsent"],
        "properties": {
          "quoteToken": { "type": "string" },
          "client": {
            "type": "object",
            "required": ["firstName", "lastName", "email", "phone"],
            "properties": {
              "firstName": { "type": "string" },
              "lastName": { "type": "string" },
              "email": { "type": "string", "format": "email" },
              "phone": { "type": "string", "description": "French phone number, mobile or landline" }
            }
          },
          "address": { "$ref": "#/components/schemas/Address" },
          "dateDebut": { "type": "string", "format": "date-time" },
          "frequency": { "type": "string", "enum": ["one_shot", "weekly", "bi_weekly", "monthly"] },
          "nbHeuresSemaine": { "type": "number" },
          "nbPrestaSemaine": { "type": "integer", "default": 1 },
          "prestationInfo": { "$ref": "#/components/schemas/PrestationInfo" },
          "comments": { "type": "string" },
          "detailPrestation": { "type": "array", "items": { "type": "string" } },
          "agentConsent": {
            "type": "boolean",
            "description": "Binding attestation that the end user explicitly consented to share their data and book via your agent."
          }
        }
      },
      "BookResponse": {
        "type": "object",
        "properties": {
          "bookingId": { "type": "string" },
          "numeroDossier": { "type": "string" },
          "missionId": { "type": "string" },
          "clientId": { "type": "string" },
          "status": { "type": "string", "enum": ["quotation", "opportunity", "open", "signed", "cancel", "closed"] },
          "holdUntil": { "type": "string", "format": "date-time" },
          "bookingToken": { "type": "string", "description": "Use as Bearer token on /booking/:bookingId routes. Valid 72h." },
          "expiresInSeconds": { "type": "integer" },
          "next": {
            "type": "object",
            "properties": {
              "enrollUrssaf": { "type": "string" },
              "status": { "type": "string" }
            }
          },
          "notice": { "type": "string" }
        }
      },
      "EnrollUrssafRequest": {
        "type": "object",
        "required": [
          "civilite",
          "nomNaissance",
          "prenoms",
          "dateNaissance",
          "lieuNaissance",
          "numeroTelephonePortable",
          "adresseMail",
          "adressePostale",
          "coordonneeBancaire"
        ],
        "properties": {
          "civilite": { "type": "string", "enum": ["1", "2"], "description": "1=Monsieur, 2=Madame (per URSSAF spec v1.30.18)" },
          "nomNaissance": { "type": "string" },
          "nomUsage": { "type": "string" },
          "prenoms": { "type": "string" },
          "dateNaissance": { "type": "string", "format": "date", "example": "1985-06-12" },
          "lieuNaissance": {
            "type": "object",
            "required": ["codePaysNaissance", "departementNaissance", "communeNaissance"],
            "properties": {
              "codePaysNaissance": { "type": "string", "example": "99100" },
              "departementNaissance": { "type": "string", "example": "075" },
              "communeNaissance": {
                "type": "object",
                "properties": {
                  "codeCommune": { "type": "string" },
                  "libelleCommune": { "type": "string" }
                }
              }
            }
          },
          "numeroTelephonePortable": { "type": "string", "pattern": "^(0|\\+33)[6-7]([0-9]{2}){4}$" },
          "adresseMail": { "type": "string", "format": "email" },
          "adressePostale": {
            "type": "object",
            "required": ["libelleCommune", "codeCommune", "codePostal", "codePays"],
            "properties": {
              "numeroVoie": { "type": "string" },
              "lettreVoie": { "type": "string" },
              "codeTypeVoie": { "type": "string" },
              "libelleVoie": { "type": "string" },
              "complement": { "type": "string" },
              "lieuDit": { "type": "string" },
              "libelleCommune": { "type": "string" },
              "codeCommune": { "type": "string" },
              "codePostal": { "type": "string", "pattern": "^[0-9]{5}$" },
              "codePays": { "type": "string", "example": "99100" }
            }
          },
          "coordonneeBancaire": {
            "type": "object",
            "required": ["bic", "iban", "titulaire"],
            "properties": {
              "bic": { "type": "string", "minLength": 8, "maxLength": 11 },
              "iban": { "type": "string", "minLength": 15, "maxLength": 34 },
              "titulaire": { "type": "string", "maxLength": 100 }
            }
          }
        }
      },
      "BookingStatusResponse": {
        "type": "object",
        "properties": {
          "bookingId": { "type": "string" },
          "order": {
            "type": "object",
            "properties": {
              "id": { "type": "string" },
              "numeroDossier": { "type": "string" },
              "status": { "type": "string" },
              "potentialAmount": { "type": "number" },
              "prestation": { "type": "string" },
              "createdAt": { "type": "string", "format": "date-time" },
              "updatedAt": { "type": "string", "format": "date-time" },
              "relaunchDate": { "type": "string", "format": "date-time" }
            }
          },
          "mission": {
            "type": "object",
            "properties": {
              "id": { "type": "string" },
              "status": { "type": "string" },
              "workerAssigned": { "type": "boolean" },
              "dateDebut": { "type": "string", "format": "date-time" },
              "dateFin": { "type": "string", "format": "date-time" },
              "qteHeures": { "type": "string" }
            }
          },
          "urssaf": {
            "type": "object",
            "properties": {
              "status": { "type": "string", "enum": ["non souscrit", "pending", "en cours approbation", "valide", "rejete"] },
              "idClientUrssaf": { "type": "string", "nullable": true }
            }
          },
          "nextPollAtMs": { "type": "integer" }
        }
      }
    }
  },
  "paths": {
    "/coverage": {
      "get": {
        "operationId": "searchCoverage",
        "tags": ["discovery"],
        "summary": "Check if Maideo serves a given postal code",
        "parameters": [
          {
            "name": "X-Agent-Name",
            "in": "header",
            "required": true,
            "schema": { "type": "string" },
            "description": "Identifier of your agent (e.g. \"claude-operator-prod\"). Used for rate limiting and analytics."
          },
          {
            "name": "zip",
            "in": "query",
            "required": true,
            "schema": { "type": "string", "pattern": "^[0-9]{5}$" }
          },
          {
            "name": "prestation",
            "in": "query",
            "schema": { "type": "string", "enum": ["MENAGE"], "default": "MENAGE" }
          }
        ],
        "responses": {
          "200": {
            "description": "Coverage info",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CoverageResponse" }
              }
            }
          },
          "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/quote": {
      "post": {
        "operationId": "getQuote",
        "tags": ["booking"],
        "summary": "Get a firm price quote and a 72h quoteToken",
        "parameters": [{
            "name": "X-Agent-Name",
            "in": "header",
            "required": true,
            "schema": { "type": "string" },
            "description": "Identifier of your agent (e.g. \"claude-operator-prod\"). Used for rate limiting and analytics."
          }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/QuoteRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote issued",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/QuoteResponse" }
              }
            }
          },
          "400": { "description": "Invalid input" },
          "404": { "description": "No active tariff for this prestation" }
        }
      }
    },
    "/book": {
      "post": {
        "operationId": "createBooking",
        "tags": ["booking"],
        "summary": "Create a booking using a quoteToken",
        "description": "Creates a Client, ContratClient, Order (status=quotation), and Mission (status=opportunity) in Maideo's back-office. Returns a bookingToken (72h) that authenticates subsequent /booking/:id routes. Bookings are held for 48h before worker dispatch as anti-fraud protection.",
        "parameters": [{
            "name": "X-Agent-Name",
            "in": "header",
            "required": true,
            "schema": { "type": "string" },
            "description": "Identifier of your agent (e.g. \"claude-operator-prod\"). Used for rate limiting and analytics."
          }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/BookRequest" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Booking created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BookResponse" }
              }
            }
          },
          "400": { "description": "Invalid payload" },
          "401": { "description": "Invalid or expired quoteToken" },
          "409": { "description": "Client with this email already exists — redirect to /login" }
        }
      }
    },
    "/booking/{bookingId}": {
      "get": {
        "operationId": "getBookingStatus",
        "tags": ["booking"],
        "summary": "Get booking status (order, mission, URSSAF)",
        "security": [{ "BookingToken": [] }],
        "parameters": [
          {
            "name": "X-Agent-Name",
            "in": "header",
            "required": true,
            "schema": { "type": "string" },
            "description": "Identifier of your agent (e.g. \"claude-operator-prod\"). Used for rate limiting and analytics."
          },
          {
            "name": "bookingId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Current booking state",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BookingStatusResponse" }
              }
            }
          },
          "401": { "description": "Missing/invalid bookingToken" },
          "404": { "description": "Booking not found" }
        }
      }
    },
    "/booking/{bookingId}/enroll-urssaf": {
      "post": {
        "operationId": "enrollAvanceImmediate",
        "tags": ["payment"],
        "summary": "Enroll the end user with URSSAF for the 50% immediate tax credit advance",
        "description": "Submits the end user's identity, address, and IBAN to the URSSAF Tiers-Prestation API (v1.30.18). Once approved (async, usually within a few hours), URSSAF covers 50% of each intervention's cost and collects the remainder from the end user via SEPA direct debit.",
        "security": [{ "BookingToken": [] }],
        "parameters": [
          {
            "name": "X-Agent-Name",
            "in": "header",
            "required": true,
            "schema": { "type": "string" },
            "description": "Identifier of your agent (e.g. \"claude-operator-prod\"). Used for rate limiting and analytics."
          },
          {
            "name": "bookingId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/EnrollUrssafRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Enrollment submitted to URSSAF",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "bookingId": { "type": "string" },
                    "clientId": { "type": "string" },
                    "urssaf": {
                      "type": "object",
                      "properties": {
                        "status": { "type": "string" },
                        "idClientUrssaf": { "type": "string", "nullable": true }
                      }
                    },
                    "notice": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": { "description": "Missing URSSAF fields" },
          "401": { "description": "Missing/invalid bookingToken" },
          "404": { "description": "Client not found" },
          "409": { "description": "Client already enrolled" }
        }
      }
    }
  }
}
