{
  "openapi": "3.1.0",
  "info": {
    "title": "OHO Taxis / ULA World API",
    "description": "Public API for querying prices, checking availability, and creating transfer bookings across all markets (UK, global). All prices are in the market's local currency (default GBP). Pickup times must be provided as ISO 8601 UTC strings.",
    "version": "1.0.0",
    "contact": {
      "name": "OHO Taxis Support",
      "url": "https://www.ulaworld.com/contact",
      "email": "info@ulaworld.com"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://www.ulaworld.com",
      "description": "Production (ULA World / OHO Taxis)"
    }
  ],
  "tags": [
    {
      "name": "Availability",
      "description": "Check vehicle availability and get real-time fixed prices for a route"
    },
    {
      "name": "Locations",
      "description": "Search for airports, addresses, cruise terminals, train stations and other pickup/drop-off points"
    },
    {
      "name": "Bookings",
      "description": "Create transfer bookings"
    }
  ],
  "paths": {
    "/api/v1/availability": {
      "post": {
        "operationId": "getAvailability",
        "summary": "Get vehicle availability and fixed prices for a route",
        "description": "Returns available vehicle classes with fixed prices for the requested route and pickup time. This is the primary endpoint for AI agents to check availability and pricing before creating a booking. Prices are fixed — no surge pricing.",
        "tags": ["Availability"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteRequest"
              },
              "example": {
                "pickup_lat": 51.4700,
                "pickup_lng": -0.4543,
                "dropoff_lat": 51.5074,
                "dropoff_lng": -0.1278,
                "pickup_time": "2026-06-15T09:00:00.000Z",
                "via_stops": []
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Available vehicles with fixed prices",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/VehicleQuote"
                  }
                },
                "example": [
                  {
                    "vehicle_class": "saloon",
                    "price": 65.00,
                    "currency": "GBP",
                    "max_passengers": 4,
                    "max_luggage": 2,
                    "distance_km": 28.4,
                    "duration_minutes": 48
                  },
                  {
                    "vehicle_class": "mpv",
                    "price": 78.00,
                    "currency": "GBP",
                    "max_passengers": 6,
                    "max_luggage": 4,
                    "distance_km": 28.4,
                    "duration_minutes": 48
                  }
                ]
              }
            }
          },
          "400": {
            "description": "Invalid request parameters",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "500": {
            "description": "Pricing service unavailable",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/api/bookings/quote": {
      "post": {
        "operationId": "getQuote",
        "summary": "Get a price quote for a transfer route",
        "description": "Returns fixed prices for all available vehicle classes on the requested route. Identical to /api/v1/availability — prefer that endpoint for new integrations.",
        "tags": ["Availability"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Vehicle prices for the route",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/VehicleQuote"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "500": {
            "description": "Pricing service unavailable",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/api/locations/search": {
      "get": {
        "operationId": "searchLocations",
        "summary": "Search for pickup or drop-off locations",
        "description": "Full-text search for airports, cruise terminals, train stations, hotels, and addresses across all supported markets. Returns structured results with coordinates and location type classification. Use the returned `lat`/`lng` values in quote and booking requests.",
        "tags": ["Locations"],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Search query (e.g. 'Heathrow Airport', 'Colombo Airport', 'Southampton Cruise Terminal')",
            "schema": {
              "type": "string",
              "minLength": 2
            },
            "example": "Heathrow Airport"
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "ISO 3166-1 alpha-2 country code to restrict results (e.g. 'GB', 'LK', 'MA', 'IM')",
            "schema": {
              "type": "string",
              "pattern": "^[A-Z]{2}$"
            },
            "example": "GB"
          },
          {
            "name": "lat",
            "in": "query",
            "required": false,
            "description": "Bias results towards this latitude (WGS 84)",
            "schema": {
              "type": "number",
              "minimum": -90,
              "maximum": 90
            }
          },
          {
            "name": "lng",
            "in": "query",
            "required": false,
            "description": "Bias results towards this longitude (WGS 84)",
            "schema": {
              "type": "number",
              "minimum": -180,
              "maximum": 180
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return (default: 10)",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Location search results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LocationSearchResponse"
                },
                "example": {
                  "results": [
                    {
                      "place_id": "lhr",
                      "name": "Heathrow Airport",
                      "address": "Heathrow Airport, Hounslow, TW6",
                      "type": "airport",
                      "lat": 51.4700,
                      "lng": -0.4543,
                      "postcode": "TW6",
                      "source": "priority",
                      "metadata": { "iata_code": "LHR" }
                    }
                  ],
                  "query": "Heathrow Airport",
                  "count": 1,
                  "providers": ["priority", "google"]
                }
              }
            }
          },
          "400": {
            "description": "Missing required query parameter",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/api/bookings/create-pending": {
      "post": {
        "operationId": "createBooking",
        "summary": "Create a transfer booking",
        "description": "Creates one or more pending bookings from a cart. Server validates prices against live quotes before committing. Returns booking IDs and reference numbers. For card payments the booking is created in `payment_pending` status; for cash/wallet it is immediately `pending`.",
        "tags": ["Bookings"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBookingRequest"
              },
              "example": {
                "cartId": "cart_abc123",
                "bookingReference": "OHO20261234",
                "paymentMethod": "card",
                "walletAmountUsed": 0,
                "bookerEmail": "passenger@example.com",
                "items": [
                  {
                    "pickup": {
                      "address": "Heathrow Airport, Hounslow, TW6",
                      "lat": 51.4700,
                      "lng": -0.4543,
                      "type": "airport"
                    },
                    "dropoff": {
                      "address": "10 Downing Street, London, SW1A 2AA",
                      "lat": 51.5034,
                      "lng": -0.1276,
                      "type": "address"
                    },
                    "pickupDateTime": "2026-06-15T09:00:00.000Z",
                    "passenger": {
                      "name": "John Smith",
                      "email": "john@example.com",
                      "phone": "+447911123456",
                      "flightNumber": "BA123"
                    },
                    "selectedVehicle": "saloon",
                    "price": 65.00,
                    "passengerCount": 2,
                    "luggageCount": 2
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bookings created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateBookingResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error (missing fields, price mismatch, invalid voucher)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "QuoteRequest": {
        "type": "object",
        "required": ["pickup_lat", "pickup_lng", "dropoff_lat", "dropoff_lng", "pickup_time"],
        "properties": {
          "pickup_lat": {
            "type": "number",
            "description": "Pickup latitude (WGS 84)",
            "example": 51.4700
          },
          "pickup_lng": {
            "type": "number",
            "description": "Pickup longitude (WGS 84)",
            "example": -0.4543
          },
          "dropoff_lat": {
            "type": "number",
            "description": "Drop-off latitude (WGS 84)",
            "example": 51.5074
          },
          "dropoff_lng": {
            "type": "number",
            "description": "Drop-off longitude (WGS 84)",
            "example": -0.1278
          },
          "pickup_time": {
            "type": "string",
            "format": "date-time",
            "description": "Requested pickup time as ISO 8601 UTC datetime",
            "example": "2026-06-15T09:00:00.000Z"
          },
          "via_stops": {
            "type": "array",
            "description": "Optional intermediate stops",
            "default": [],
            "items": {
              "type": "object",
              "required": ["lat", "lng"],
              "properties": {
                "lat": { "type": "number" },
                "lng": { "type": "number" }
              }
            }
          }
        }
      },
      "VehicleQuote": {
        "type": "object",
        "properties": {
          "vehicle_class": {
            "type": "string",
            "description": "Vehicle class identifier",
            "enum": ["saloon", "estate", "mpv", "mpv7", "mpv8", "executive", "minibus12", "minibus15"]
          },
          "price": {
            "type": "number",
            "description": "Fixed price in local currency for this vehicle class on this route",
            "example": 65.00
          },
          "currency": {
            "type": "string",
            "description": "Currency code (e.g. GBP, LKR, MAD)",
            "example": "GBP"
          },
          "max_passengers": {
            "type": "integer",
            "description": "Maximum passenger capacity",
            "example": 4
          },
          "max_luggage": {
            "type": "integer",
            "description": "Maximum standard luggage items",
            "example": 2
          },
          "distance_km": {
            "type": "number",
            "description": "Calculated route distance in kilometres",
            "example": 28.4
          },
          "duration_minutes": {
            "type": "integer",
            "description": "Estimated journey time in minutes",
            "example": 48
          }
        }
      },
      "LocationSearchResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchResult"
            }
          },
          "query": {
            "type": "string",
            "description": "The original search query"
          },
          "count": {
            "type": "integer",
            "description": "Total number of results returned"
          },
          "providers": {
            "type": "array",
            "description": "Data sources used (e.g. priority, google, fixed_routes, iom_addresses)",
            "items": { "type": "string" }
          }
        }
      },
      "SearchResult": {
        "type": "object",
        "properties": {
          "place_id": {
            "type": "string",
            "description": "Unique identifier for the location (Google Place ID or internal ID)",
            "example": "lhr"
          },
          "name": {
            "type": "string",
            "description": "Display name of the location",
            "example": "Heathrow Airport"
          },
          "address": {
            "type": "string",
            "description": "Formatted address",
            "example": "Heathrow Airport, Hounslow, TW6"
          },
          "type": {
            "type": "string",
            "description": "Location type classification",
            "enum": [
              "airport",
              "airport_terminal",
              "cruise_port",
              "cruise_terminal",
              "train_station",
              "station",
              "hotel",
              "venue",
              "beach",
              "address",
              "postcode",
              "poi",
              "other"
            ]
          },
          "lat": {
            "type": "number",
            "description": "Latitude (WGS 84)",
            "example": 51.4700
          },
          "lng": {
            "type": "number",
            "description": "Longitude (WGS 84)",
            "example": -0.4543
          },
          "postcode": {
            "type": "string",
            "description": "Postcode (if available)",
            "example": "TW6"
          },
          "source": {
            "type": "string",
            "description": "Data source for this result",
            "enum": ["priority", "google", "fixed_routes", "iom_addresses", "mock"],
            "example": "priority"
          },
          "metadata": {
            "type": "object",
            "description": "Additional location metadata (e.g. IATA codes for airports)",
            "properties": {
              "iata_code": {
                "type": "string",
                "description": "IATA airport code",
                "example": "LHR"
              }
            }
          }
        }
      },
      "CreateBookingRequest": {
        "type": "object",
        "required": ["items", "cartId", "bookingReference", "paymentMethod"],
        "properties": {
          "cartId": {
            "type": "string",
            "description": "Unique cart session identifier",
            "example": "cart_abc123"
          },
          "bookingReference": {
            "type": "string",
            "description": "Human-readable booking reference (e.g. OHO20261234)",
            "example": "OHO20261234"
          },
          "paymentMethod": {
            "type": "string",
            "description": "Payment method. 'card' creates a payment_pending booking awaiting Stripe charge. 'cash' books immediately for driver payment. 'wallet' deducts from the customer's loyalty wallet.",
            "enum": ["card", "cash", "wallet", "account"]
          },
          "walletAmountUsed": {
            "type": "number",
            "description": "Amount to deduct from the customer's loyalty wallet (0 if not using wallet)",
            "default": 0
          },
          "bookerEmail": {
            "type": "string",
            "format": "email",
            "description": "Email address of the logged-in booker (may differ from passenger for group bookings)"
          },
          "bookerUserId": {
            "type": "string",
            "description": "UUID of the logged-in booker (preferred over bookerEmail for stability)"
          },
          "voucherDiscount": {
            "type": "number",
            "description": "Voucher discount amount (server re-validates before applying)"
          },
          "returnDiscount": {
            "type": "number",
            "description": "Return journey discount amount"
          },
          "items": {
            "type": "array",
            "description": "One item per journey leg (single transfer = 1 item, return trip = 2 items)",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/BookingItem"
            }
          }
        }
      },
      "BookingItem": {
        "type": "object",
        "required": ["pickup", "dropoff", "pickupDateTime", "passenger", "price"],
        "properties": {
          "pickup": {
            "$ref": "#/components/schemas/BookingLocation"
          },
          "dropoff": {
            "$ref": "#/components/schemas/BookingLocation"
          },
          "pickupDateTime": {
            "type": "string",
            "format": "date-time",
            "description": "Pickup time as ISO 8601 UTC datetime",
            "example": "2026-06-15T09:00:00.000Z"
          },
          "passenger": {
            "$ref": "#/components/schemas/Passenger"
          },
          "selectedVehicle": {
            "type": "string",
            "description": "Vehicle class selected by the passenger",
            "enum": ["saloon", "estate", "mpv", "mpv7", "mpv8", "executive", "minibus12", "minibus15"]
          },
          "price": {
            "type": "number",
            "description": "Total journey price (server validates this against live quote — must match within 5%)",
            "example": 65.00
          },
          "passengerCount": {
            "type": "integer",
            "minimum": 1,
            "default": 1
          },
          "luggageCount": {
            "type": "integer",
            "minimum": 0,
            "default": 0
          },
          "handLuggageCount": {
            "type": "integer",
            "minimum": 0,
            "default": 0
          },
          "viaStops": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/BookingLocation" }
          },
          "type": {
            "type": "string",
            "enum": ["single", "return"]
          },
          "isTour": {
            "type": "boolean",
            "default": false
          },
          "tourName": {
            "type": "string"
          },
          "voucherCode": {
            "type": "string"
          },
          "babySeats": {
            "type": "object",
            "properties": {
              "infantSeats": { "type": "integer", "minimum": 0 },
              "childSeats": { "type": "integer", "minimum": 0 },
              "boosterSeats": { "type": "integer", "minimum": 0 }
            }
          },
          "extras": {
            "type": "object",
            "properties": {
              "skis": { "type": "integer", "minimum": 0 },
              "surfboards": { "type": "integer", "minimum": 0 }
            }
          },
          "distanceKm": { "type": "number" },
          "durationMinutes": { "type": "integer" },
          "cancellationProtection": { "type": "boolean", "default": false },
          "cancellationProtectionFee": { "type": "number" }
        }
      },
      "BookingLocation": {
        "type": "object",
        "required": ["address", "lat", "lng"],
        "properties": {
          "address": {
            "type": "string",
            "example": "Heathrow Airport, Hounslow, TW6"
          },
          "lat": { "type": "number", "example": 51.4700 },
          "lng": { "type": "number", "example": -0.4543 },
          "placeId": { "type": "string" },
          "type": {
            "type": "string",
            "enum": ["airport", "airport_terminal", "cruise_port", "cruise_terminal", "train_station", "station", "hotel", "address", "postcode", "poi", "other"]
          },
          "postcode": { "type": "string" }
        }
      },
      "Passenger": {
        "type": "object",
        "required": ["name", "email", "phone"],
        "properties": {
          "name": { "type": "string", "example": "John Smith" },
          "email": { "type": "string", "format": "email" },
          "phone": { "type": "string", "example": "+447911123456" },
          "flightNumber": { "type": "string", "example": "BA123" },
          "dropOffFlightNumber": { "type": "string" },
          "flightLandingTime": {
            "type": "string",
            "description": "Flight/ship landing time in HH:mm local time",
            "example": "08:30"
          },
          "sendAfterLanded": {
            "type": "string",
            "description": "Minutes after landing to dispatch driver",
            "example": "60"
          },
          "cruiseNumber": { "type": "string" },
          "ferryName": { "type": "string" },
          "specialRequests": { "type": "string" },
          "note_to_driver": { "type": "string" },
          "isForSomeoneElse": { "type": "boolean", "default": false }
        }
      },
      "CreateBookingResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "cartId": { "type": "string" },
          "bookingReference": { "type": "string" },
          "bookingIds": {
            "type": "array",
            "items": { "type": "string", "format": "uuid" }
          },
          "totalAmount": { "type": "number", "example": 65.00 }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "details": { "type": "string" }
        }
      }
    }
  }
}
