{
  "openapi": "3.1.0",
  "info": {
    "title": "BA Transfer API",
    "description": "Public API for querying prices, checking availability, and creating transfer bookings. All prices are in GBP. Pickup times must be provided as ISO 8601 UTC strings.",
    "version": "1.0.0",
    "contact": {
      "name": "BA Transfer Support",
      "url": "https://www.batransfer.com/contact",
      "email": "info@batransfer.com"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://www.batransfer.com",
      "description": "Production"
    }
  ],
  "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": "estate",
                    "price": 72.00,
                    "currency": "GBP",
                    "max_passengers": 4,
                    "max_luggage": 4,
                    "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. 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', 'London Victoria', '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', '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" }
                    },
                    {
                      "place_id": "lhr-t2",
                      "name": "Heathrow Terminal 2",
                      "address": "Heathrow Airport Terminal 2, Hounslow, TW6 1EW",
                      "type": "airport_terminal",
                      "lat": 51.4775,
                      "lng": -0.4614,
                      "postcode": "TW6 1EW",
                      "source": "priority"
                    }
                  ],
                  "query": "Heathrow Airport",
                  "count": 2,
                  "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": "BA20261234",
                "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 GBP for this vehicle class on this route",
            "example": 65.00
          },
          "currency": {
            "type": "string",
            "description": "Currency code (always GBP)",
            "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, 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": "UK postcode (if available)",
            "example": "TW6"
          },
          "source": {
            "type": "string",
            "description": "Data source for this result",
            "enum": ["priority", "google", "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. BA20261234)",
            "example": "BA20261234"
          },
          "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 in GBP 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 in GBP (server re-validates before applying)"
          },
          "returnDiscount": {
            "type": "number",
            "description": "Return journey discount amount in GBP"
          },
          "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 in GBP (server validates this against live quote — must match within 5%)",
            "example": 65.00
          },
          "passengerCount": {
            "type": "integer",
            "description": "Number of passengers",
            "minimum": 1,
            "default": 1
          },
          "luggageCount": {
            "type": "integer",
            "description": "Number of standard luggage items",
            "minimum": 0,
            "default": 0
          },
          "handLuggageCount": {
            "type": "integer",
            "description": "Number of hand luggage / cabin bag items",
            "minimum": 0,
            "default": 0
          },
          "viaStops": {
            "type": "array",
            "description": "Optional intermediate stops",
            "items": {
              "$ref": "#/components/schemas/BookingLocation"
            }
          },
          "type": {
            "type": "string",
            "description": "Journey type ('single' or 'return' for return-leg items)",
            "enum": ["single", "return"]
          },
          "isTour": {
            "type": "boolean",
            "description": "True for tour bookings (price not re-validated against quote API)",
            "default": false
          },
          "tourName": {
            "type": "string",
            "description": "Tour name (required when isTour is true)"
          },
          "voucherCode": {
            "type": "string",
            "description": "Promotional voucher code to apply to this item"
          },
          "babySeats": {
            "type": "object",
            "description": "Baby/child seat requirements",
            "properties": {
              "infantSeats": { "type": "integer", "minimum": 0 },
              "childSeats": { "type": "integer", "minimum": 0 },
              "boosterSeats": { "type": "integer", "minimum": 0 }
            }
          },
          "extras": {
            "type": "object",
            "description": "Additional equipment",
            "properties": {
              "skis": { "type": "integer", "minimum": 0 },
              "surfboards": { "type": "integer", "minimum": 0 }
            }
          },
          "distanceKm": {
            "type": "number",
            "description": "Route distance in kilometres (from quote response)"
          },
          "durationMinutes": {
            "type": "integer",
            "description": "Estimated journey duration in minutes (from quote response)"
          },
          "checkInBoardingService": {
            "type": "boolean",
            "description": "Customer has requested check-in / boarding assistance service",
            "default": false
          },
          "checkInBoardingFee": {
            "type": "number",
            "description": "Fee for check-in/boarding service in GBP (default: 35)"
          },
          "cancellationProtection": {
            "type": "boolean",
            "description": "Customer has purchased cancellation protection",
            "default": false
          },
          "cancellationProtectionFee": {
            "type": "number",
            "description": "Fee for cancellation protection in GBP"
          }
        }
      },
      "BookingLocation": {
        "type": "object",
        "required": ["address", "lat", "lng"],
        "properties": {
          "address": {
            "type": "string",
            "description": "Formatted address string",
            "example": "Heathrow Airport, Hounslow, TW6"
          },
          "lat": {
            "type": "number",
            "description": "Latitude (WGS 84)",
            "example": 51.4700
          },
          "lng": {
            "type": "number",
            "description": "Longitude (WGS 84)",
            "example": -0.4543
          },
          "placeId": {
            "type": "string",
            "description": "Place ID (from location search results)",
            "example": "lhr"
          },
          "type": {
            "type": "string",
            "description": "Location type",
            "enum": [
              "airport",
              "airport_terminal",
              "cruise_port",
              "cruise_terminal",
              "train_station",
              "station",
              "hotel",
              "address",
              "postcode",
              "poi",
              "other"
            ]
          },
          "postcode": {
            "type": "string",
            "description": "UK postcode (if available)",
            "example": "TW6"
          }
        }
      },
      "Passenger": {
        "type": "object",
        "required": ["name", "email", "phone"],
        "properties": {
          "name": {
            "type": "string",
            "description": "Full name of the passenger",
            "example": "John Smith"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Passenger email address (used for confirmation emails)",
            "example": "john@example.com"
          },
          "phone": {
            "type": "string",
            "description": "Passenger phone number in E.164 format",
            "example": "+447911123456"
          },
          "flightNumber": {
            "type": "string",
            "description": "Arrival flight number (required for airport pickup transfers)",
            "example": "BA123"
          },
          "dropOffFlightNumber": {
            "type": "string",
            "description": "Departure flight number (for airport drop-off transfers)"
          },
          "flightLandingTime": {
            "type": "string",
            "description": "Flight landing time in HH:mm format (UK local time) — used to calculate actual pickup time at airports and cruise ports",
            "example": "08:30"
          },
          "sendAfterLanded": {
            "type": "string",
            "description": "Minutes to wait after landing before dispatching the driver (e.g. '45', '60', '90')",
            "example": "60"
          },
          "cruiseNumber": {
            "type": "string",
            "description": "Cruise ship voyage number (for cruise port pickups)"
          },
          "ferryName": {
            "type": "string",
            "description": "Ferry or ship name (for cruise/ferry port pickups)"
          },
          "specialRequests": {
            "type": "string",
            "description": "Free-text special instructions for the booking"
          },
          "note_to_driver": {
            "type": "string",
            "description": "Message shown directly to the driver"
          },
          "isForSomeoneElse": {
            "type": "boolean",
            "description": "True when the logged-in user is booking on behalf of another person",
            "default": false
          }
        }
      },
      "CreateBookingResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "cartId": {
            "type": "string",
            "description": "The cart ID from the request",
            "example": "cart_abc123"
          },
          "bookingReference": {
            "type": "string",
            "description": "The booking reference from the request",
            "example": "BA20261234"
          },
          "bookingIds": {
            "type": "array",
            "description": "Database UUIDs for each created booking",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "totalAmount": {
            "type": "number",
            "description": "Net total amount due in GBP (after wallet and voucher deductions)",
            "example": 65.00
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message",
            "example": "Missing required data: items, cartId, bookingReference"
          },
          "details": {
            "type": "string",
            "description": "Additional error detail"
          }
        }
      }
    }
  }
}
