{
  "openapi": "3.1.0",
  "info": {
    "title": "Privyde Public API",
    "version": "1.1.0",
    "summary": "Lead-capture and AI-concierge endpoints for Privyde executive transport.",
    "description": "Public endpoints an AI agent can use to request an executive transfer, contact the team, open a corporate/hotel account, or talk to the concierge assistant. Submissions are leads: a human confirms the chauffeur and final price by email/WhatsApp. Admin and Stripe-webhook endpoints are intentionally NOT part of this contract. The public surface is versioned in the URL path: /api/v1/... is the stable address; /api/... is kept as an alias of the current version. Breaking changes ship as a new path version and the retiring one announces itself with Deprecation and Sunset headers at least 180 days in advance.",
    "contact": {
      "name": "Privyde",
      "email": "info@privyde.com",
      "url": "https://privyde.com"
    },
    "x-api-version": "1",
    "x-versioning": {
      "strategy": "url-path",
      "current": "v1",
      "currentBaseUrl": "https://privyde.com/api/v1",
      "unversionedAlias": "https://privyde.com/api",
      "versionHeader": "API-Version",
      "deprecationHeaders": [
        "Deprecation",
        "Sunset",
        "Link; rel=\"deprecation\""
      ],
      "minimumNoticeDays": 180,
      "policy": "https://privyde.com/developers/deprecation"
    },
    "x-rate-limit": {
      "policy": "default;q=60;w=60",
      "writeEndpoints": "30 requests per minute per IP",
      "headers": [
        "RateLimit",
        "RateLimit-Limit",
        "RateLimit-Remaining",
        "RateLimit-Reset",
        "Retry-After"
      ],
      "documentation": "https://privyde.com/developers#rate-limits"
    },
    "x-deprecation-policy": "https://privyde.com/developers/deprecation"
  },
  "servers": [
    {
      "url": "https://privyde.com",
      "description": "Production"
    }
  ],
  "paths": {
    "/api/v1/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Liveness/status probe",
        "description": "Returns 200 with a small status payload. Advertised as rel=\"status\" in the API catalog.",
        "responses": {
          "200": {
            "description": "Service is up",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            }
          },
          "404": {
            "description": "No such endpoint.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/booking": {
      "post": {
        "operationId": "createBooking",
        "summary": "Request an executive transfer",
        "description": "Creates a transfer booking request. Triggers an email to the operations team and a confirmation email to the customer. The booking is recorded as `pending` until a human confirms the chauffeur.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Booking request accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            }
          },
          "500": {
            "description": "Validation or processing error",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing, not JSON, or fails validation.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such endpoint.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/contact": {
      "post": {
        "operationId": "sendContactMessage",
        "summary": "Send a contact message",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            }
          },
          "500": {
            "description": "Validation or processing error",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing, not JSON, or fails validation.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such endpoint.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/corporate": {
      "post": {
        "operationId": "requestCorporateAccount",
        "summary": "Request a corporate / hotel account",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CorporateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Request accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            }
          },
          "500": {
            "description": "Validation or processing error",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request body is missing, not JSON, or fails validation.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such endpoint.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/chat": {
      "post": {
        "operationId": "concierge",
        "summary": "Talk to the AI concierge assistant",
        "description": "Multi-turn assistant (Google Gemini) that can quote prices and help draft a booking. Send the full message history each turn.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Assistant reply",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatResponse"
                }
              }
            },
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            }
          },
          "400": {
            "description": "Missing or malformed messages",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Processing error",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such endpoint.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/quote": {
      "post": {
        "operationId": "createQuote",
        "summary": "Price a transfer, an hourly disposal or a multi-day service",
        "description": "Read-only: computes a price from the published rate card and stores nothing the customer can be charged for. Call this before /api/v1/booking so the user sees a real price.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Price computed",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuoteResponse"
                }
              }
            }
          },
          "400": {
            "description": "The quote request fails validation.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Honour Retry-After.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "The quote could not be computed.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/mcp": {
      "post": {
        "operationId": "mcpJsonRpc",
        "summary": "MCP server (JSON-RPC 2.0 over Streamable HTTP)",
        "description": "Hosted Model Context Protocol server with read-only tools: list_services, list_cities, list_fleet, list_popular_routes, get_company_info, contact_concierge and get_booking_link. Discovery card at /.well-known/mcp/server-card.json.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "JSON-RPC 2.0 request.",
                "properties": {
                  "jsonrpc": {
                    "type": "string",
                    "example": "2.0"
                  },
                  "id": {},
                  "method": {
                    "type": "string",
                    "example": "tools/list"
                  },
                  "params": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "405": {
            "description": "GET is not supported: this server offers no server-to-client stream. POST JSON-RPC instead.",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api": {
      "get": {
        "operationId": "getApiIndex",
        "summary": "Machine-readable index of the public API",
        "description": "Endpoints, documentation links, versioning policy, rate limits and error format as JSON.",
        "responses": {
          "200": {
            "description": "API index",
            "headers": {
              "RateLimit": {
                "description": "Remaining quota for the current window (IETF structured field).",
                "schema": {
                  "type": "string"
                },
                "example": "limit=30, remaining=27, reset=41"
              },
              "RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "description": "Version of the public API that produced this response.",
                "schema": {
                  "type": "string"
                },
                "example": "1"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Health": {
        "type": "object",
        "required": [
          "status",
          "service",
          "time"
        ],
        "properties": {
          "status": {
            "type": "string",
            "examples": [
              "ok"
            ]
          },
          "service": {
            "type": "string",
            "examples": [
              "privyde-web"
            ]
          },
          "version": {
            "type": "string",
            "examples": [
              "0.1.0"
            ]
          },
          "time": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BookingRequest": {
        "type": "object",
        "required": [
          "name",
          "email",
          "phone",
          "origin",
          "destination",
          "date",
          "time",
          "passengers",
          "luggage"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string",
            "minLength": 10,
            "description": "International format recommended, e.g. +34..."
          },
          "origin": {
            "type": "string",
            "minLength": 3,
            "description": "Pickup location (address, airport, hotel)."
          },
          "destination": {
            "type": "string",
            "minLength": 3
          },
          "date": {
            "type": "string",
            "description": "Pickup date, ISO 8601 (YYYY-MM-DD)."
          },
          "time": {
            "type": "string",
            "description": "Pickup time, 24h HH:MM."
          },
          "passengers": {
            "type": "integer",
            "minimum": 1,
            "maximum": 6
          },
          "luggage": {
            "type": "string",
            "enum": [
              "light",
              "normal",
              "heavy"
            ]
          },
          "message": {
            "type": "string",
            "description": "Optional notes (flight number, child seat, etc.)."
          },
          "lang": {
            "type": "string",
            "enum": [
              "es",
              "en"
            ],
            "default": "es"
          }
        }
      },
      "ContactRequest": {
        "type": "object",
        "required": [
          "name",
          "email",
          "phone",
          "subject",
          "message"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string",
            "minLength": 10
          },
          "subject": {
            "type": "string",
            "minLength": 3
          },
          "message": {
            "type": "string",
            "minLength": 10
          },
          "lang": {
            "type": "string",
            "enum": [
              "es",
              "en"
            ],
            "default": "es"
          }
        }
      },
      "CorporateRequest": {
        "type": "object",
        "required": [
          "company",
          "contactName",
          "position",
          "email",
          "phone",
          "needType",
          "message"
        ],
        "properties": {
          "company": {
            "type": "string",
            "minLength": 2
          },
          "contactName": {
            "type": "string",
            "minLength": 2
          },
          "position": {
            "type": "string",
            "minLength": 2
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string",
            "minLength": 10
          },
          "needType": {
            "type": "string",
            "enum": [
              "event",
              "regular",
              "hotel",
              "other"
            ]
          },
          "message": {
            "type": "string",
            "minLength": 10
          },
          "lang": {
            "type": "string",
            "enum": [
              "es",
              "en"
            ],
            "default": "es"
          }
        }
      },
      "ChatMessage": {
        "type": "object",
        "required": [
          "role",
          "content"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "user",
              "model"
            ]
          },
          "content": {
            "type": "string"
          }
        }
      },
      "ChatRequest": {
        "type": "object",
        "required": [
          "messages"
        ],
        "properties": {
          "messages": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/ChatMessage"
            }
          },
          "lang": {
            "type": "string",
            "enum": [
              "es",
              "en"
            ],
            "default": "es"
          }
        }
      },
      "ChatResponse": {
        "type": "object",
        "required": [
          "response"
        ],
        "properties": {
          "response": {
            "type": "string"
          },
          "bookingCode": {
            "type": "string",
            "description": "Present when the assistant created a booking during the turn."
          }
        }
      },
      "MessageResponse": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "description": "Every error under /api is JSON with this shape. `code` is stable and safe to program against; `hint` says what to do next. The flat `type`/`title`/`status`/`detail` members mirror RFC 9457 problem details.",
        "required": [
          "error",
          "code",
          "message",
          "hint",
          "status"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Stable machine code (same value as `code`).",
            "example": "invalid_request"
          },
          "code": {
            "type": "string",
            "enum": [
              "invalid_json",
              "invalid_request",
              "not_found",
              "method_not_allowed",
              "rate_limited",
              "delivery_failed",
              "quote_failed",
              "internal_error",
              "unavailable"
            ],
            "example": "invalid_request"
          },
          "message": {
            "type": "string",
            "description": "Human readable explanation.",
            "example": "The booking request failed validation."
          },
          "hint": {
            "type": "string",
            "description": "What the client should do next.",
            "example": "Fix the fields listed in `details` and retry."
          },
          "docs": {
            "type": "string",
            "format": "uri",
            "example": "https://privyde.com/developers"
          },
          "status": {
            "type": "integer",
            "example": 400
          },
          "details": {
            "type": "array",
            "description": "Field-level validation errors, when applicable.",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          },
          "type": {
            "type": "string",
            "format": "uri",
            "description": "RFC 9457 problem type."
          },
          "title": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          }
        }
      },
      "QuoteRequest": {
        "type": "object",
        "required": [
          "serviceType",
          "tier",
          "origin",
          "date",
          "time",
          "passengers"
        ],
        "description": "Input for the price engine. `destination` is required for SRV_TRANSFER and SRV_LONG_DISTANCE; SRV_DISPOSITION needs `durationHours` (4-12) or `durationDays`.",
        "properties": {
          "serviceType": {
            "type": "string",
            "enum": [
              "SRV_TRANSFER",
              "SRV_DISPOSITION",
              "SRV_LONG_DISTANCE"
            ],
            "description": "SRV_TRANSFER = point to point within 45 km of central Madrid; SRV_DISPOSITION = chauffeur at disposal by the hour or by the day; SRV_LONG_DISTANCE = intercity, in either direction."
          },
          "tier": {
            "type": "string",
            "enum": [
              "TIER_STANDARD",
              "TIER_ATELIER"
            ],
            "description": "Vehicle class the price is computed for."
          },
          "origin": {
            "type": "string",
            "minLength": 2,
            "maxLength": 200,
            "example": "Adolfo Suárez Madrid-Barajas Airport, Madrid"
          },
          "destination": {
            "type": "string",
            "maxLength": 200,
            "example": "Puerta del Sol, Madrid"
          },
          "originLocality": {
            "type": "string",
            "maxLength": 120,
            "description": "Town resolved by an address autocomplete, when available. Keeps a province from being mistaken for a city."
          },
          "destinationLocality": {
            "type": "string",
            "maxLength": 120
          },
          "date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "example": "2026-09-15"
          },
          "time": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}$",
            "example": "09:30"
          },
          "passengers": {
            "type": "integer",
            "minimum": 1,
            "maximum": 60,
            "example": 2
          },
          "durationHours": {
            "type": "integer",
            "minimum": 4,
            "maximum": 12,
            "description": "Hours at disposal (SRV_DISPOSITION)."
          },
          "durationDays": {
            "type": "integer",
            "minimum": 1,
            "maximum": 30,
            "description": "Days at disposal (SRV_DISPOSITION, from 2 days)."
          },
          "oneWayOrRoundTrip": {
            "type": "string",
            "enum": [
              "one_way",
              "round_trip"
            ]
          },
          "addons": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "ADN_MEET_AND_GREET",
                "ADN_CHILD_SEAT",
                "ADN_MULTILINGUAL_DRIVER"
              ]
            }
          },
          "flightNumber": {
            "type": "string",
            "maxLength": 16
          },
          "vehicle": {
            "type": "string",
            "maxLength": 120,
            "description": "Preferred vehicle slug. An unknown slug is ignored, never an error."
          },
          "lang": {
            "type": "string",
            "enum": [
              "es",
              "en",
              "fr",
              "de",
              "it",
              "pt"
            ]
          }
        }
      },
      "QuoteResponse": {
        "type": "object",
        "description": "Prices come from the published rate card, never from a per-request estimate. When a trip needs human validation the answer says so instead of returning a number.",
        "properties": {
          "quoteId": {
            "type": "string",
            "example": "PRV-Q-2026-A1B2C3D4"
          },
          "validUntil": {
            "type": "string",
            "format": "date-time"
          },
          "ttlMs": {
            "type": "integer"
          },
          "quote": {
            "type": "object",
            "description": "Computed price, currency, service class and any human-validation flag."
          },
          "tierTotals": {
            "type": "object",
            "description": "Total per vehicle tier (standard / atelier)."
          }
        }
      }
    }
  },
  "externalDocs": {
    "description": "Privyde developer portal",
    "url": "https://privyde.com/developers"
  }
}