{
  "openapi": "3.1.0",
  "info": {
    "title": "Airport Pickups London — Transfer Booking API",
    "description": "Get quotes, validate flights, and book airport, cruise port, and point-to-point transfers across the UK. Covers all London airports (Heathrow, Gatwick, Stansted, Luton, City Airport), Edinburgh Airport, major cruise ports (Southampton, Dover, Portsmouth), and any UK address or postcode. Fixed prices, no surge pricing, free cancellation 12+ hours before pickup.",
    "version": "1.1.0",
    "contact": {
      "name": "Airport Pickups London",
      "url": "https://www.airport-pickups-london.com",
      "email": "support@aplcars.com"
    }
  },
  "servers": [
    {
      "url": "https://mcp.airport-pickups-london.com",
      "description": "Production"
    }
  ],
  "paths": {
    "/api/quote": {
      "post": {
        "operationId": "getTransferQuote",
        "summary": "Get transfer prices for any UK route",
        "description": "Returns available car types with prices, passenger capacity, and luggage capacity for a transfer between any two UK locations. Use this to show customers their options before booking.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["origin", "destination"],
                "properties": {
                  "origin": {
                    "type": "string",
                    "description": "Pickup location — airport name (e.g. 'Heathrow', 'Gatwick'), cruise port, address, hotel name, or UK postcode",
                    "examples": ["Heathrow Terminal 2", "SW1A 1AA", "The Ritz London"]
                  },
                  "destination": {
                    "type": "string",
                    "description": "Dropoff location — address, postcode, hotel name, airport, or area name",
                    "examples": ["W1K 1LN", "Gatwick South", "Hilton Park Lane"]
                  },
                  "passengers": {
                    "type": "integer",
                    "description": "Number of passengers (default 1)",
                    "default": 1,
                    "minimum": 1,
                    "maximum": 50
                  },
                  "suitcases": {
                    "type": "integer",
                    "description": "Number of suitcases/large bags (default 1)",
                    "default": 1,
                    "minimum": 0,
                    "maximum": 50
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote with available car types and prices",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "pickup": { "type": "string", "description": "Resolved pickup location name" },
                    "dropoff": { "type": "string", "description": "Resolved dropoff location name" },
                    "passengers": { "type": "integer" },
                    "recommended_car_type": { "type": "string", "description": "Recommended car based on passenger/luggage count" },
                    "cars": {
                      "type": "array",
                      "description": "Available car types with prices",
                      "items": {
                        "type": "object",
                        "properties": {
                          "car_type": { "type": "string", "description": "Vehicle type name (e.g. Saloon, People Carrier, 8 Seater)" },
                          "price_gbp": { "type": "number", "description": "Fixed price in GBP" },
                          "max_passengers": { "type": "integer", "description": "Maximum passenger capacity" },
                          "max_bags": { "type": "integer", "description": "Maximum suitcase capacity" }
                        }
                      }
                    },
                    "meeting_point": {
                      "type": "object",
                      "description": "Driver meeting instructions for airport/station pickups",
                      "properties": {
                        "name": { "type": "string" },
                        "instructions": { "type": "string" }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or route not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/validate-flight": {
      "post": {
        "operationId": "validateFlight",
        "summary": "Validate a flight number and get terminal, airline, and arrival time",
        "description": "Validates a flight number and returns airline, arrival airport, terminal, and time. Use before booking to verify the flight and auto-detect terminal. Never blocks booking.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["flight_number", "date"],
                "properties": {
                  "flight_number": {
                    "type": "string",
                    "description": "Flight number (e.g. 'BA2534', 'EK007', 'AA38')",
                    "examples": ["BA2534", "EK007", "AA38"]
                  },
                  "date": {
                    "type": "string",
                    "description": "Flight date in YYYY-MM-DD format",
                    "examples": ["2026-04-15"]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Flight validation result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "valid": { "type": "boolean", "description": "Whether the flight was found and verified" },
                    "airline": { "type": "string", "description": "Airline name (e.g. 'British Airways')" },
                    "flight_number": { "type": "string", "description": "Normalised flight number (e.g. 'BA2534')" },
                    "arrival_airport": { "type": "string", "description": "Arrival airport name (e.g. 'Heathrow')" },
                    "arrival_terminal": { "type": "string", "description": "Arrival terminal (e.g. 'T5', 'North')" },
                    "arrival_time": { "type": "string", "description": "Scheduled arrival time in HH:MM format" },
                    "departure_airport": { "type": "string", "description": "Departure airport IATA code (e.g. 'JFK')" },
                    "source": { "type": "string", "description": "Data source — 'flightstats' (live API) or 'static_mapping' (airline terminal database)" },
                    "message": { "type": "string", "description": "Error message when valid is false" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/book": {
      "post": {
        "operationId": "createTransferBooking",
        "summary": "Book a transfer — creates a real reservation",
        "description": "Creates a real transfer booking. Only call after customer confirms details and price. For airport pickups, provide flight_number to auto-detect terminal. Bookings created as cash, online payment via manage_booking_url.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["origin", "destination", "transfer_date", "transfer_time", "passenger_name", "passenger_phone"],
                "properties": {
                  "origin": {
                    "type": "string",
                    "description": "Pickup location — airport name, address, or postcode"
                  },
                  "destination": {
                    "type": "string",
                    "description": "Dropoff location — address, postcode, or airport name"
                  },
                  "transfer_date": {
                    "type": "string",
                    "description": "Transfer date in YYYY-MM-DD format",
                    "examples": ["2026-04-15"]
                  },
                  "transfer_time": {
                    "type": "string",
                    "description": "Pickup time in HH:MM format (24-hour). For airport pickups this is when the customer wants to be collected (after landing + customs). The system calculates the waiting time from the flight landing time automatically.",
                    "examples": ["14:30"]
                  },
                  "passengers": {
                    "type": "integer",
                    "description": "Number of passengers (default 1)",
                    "default": 1
                  },
                  "suitcases": {
                    "type": "integer",
                    "description": "Number of suitcases (default 1)",
                    "default": 1
                  },
                  "car_type": {
                    "type": "string",
                    "description": "Car type from the quote (e.g. 'Saloon', 'People Carrier', '8 Seater', 'Executive Saloon')"
                  },
                  "passenger_name": {
                    "type": "string",
                    "description": "Full name of the passenger"
                  },
                  "passenger_phone": {
                    "type": "string",
                    "description": "Phone number with country code (e.g. +447123456789)"
                  },
                  "passenger_email": {
                    "type": "string",
                    "description": "Email address for booking confirmation and manage booking link"
                  },
                  "flight_number": {
                    "type": "string",
                    "description": "Flight number for airport pickups (e.g. BA2534). System validates and auto-detects terminal. Required for airport pickups, optional for dropoffs."
                  },
                  "cruise_name": {
                    "type": "string",
                    "description": "Cruise/ship name for cruise port transfers"
                  },
                  "train_number": {
                    "type": "string",
                    "description": "Train number or origin for station pickups"
                  },
                  "door_number": {
                    "type": "string",
                    "description": "House/building number for postcode destinations"
                  },
                  "special_requests": {
                    "type": "string",
                    "description": "Special requirements (e.g. 'child seat needed — forward-facing (age 3)', 'wheelchair')"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Booking confirmed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "booking_reference": { "type": "string", "description": "Booking reference: APL- + the London Tech reservation id (e.g. APL-1013869); reservation_id carries the bare number" },
                    "status": { "type": "string", "description": "Booking status — 'confirmed'" },
                    "pickup": { "type": "string" },
                    "dropoff": { "type": "string" },
                    "date": { "type": "string" },
                    "time": { "type": "string" },
                    "car_type": { "type": "string" },
                    "price_gbp": { "type": "number" },
                    "passengers": { "type": "integer" },
                    "manage_booking_url": { "type": "string", "description": "Link to track driver, manage booking, and pay online (card, Apple Pay, Google Pay, PayPal, Revolut, WeChat Pay, AliPay)" },
                    "meeting_point": {
                      "type": "object",
                      "properties": {
                        "name": { "type": "string", "description": "Terminal/location name (e.g. 'Heathrow T2')" },
                        "instructions": { "type": "string", "description": "Where to meet the driver" }
                      }
                    },
                    "flight_info": {
                      "type": "object",
                      "nullable": true,
                      "description": "Flight details from automatic validation (included when flight_number was provided and verified for airport pickups)",
                      "properties": {
                        "airline": { "type": "string", "description": "Airline name (e.g. 'British Airways')" },
                        "flight_number": { "type": "string", "description": "Normalised flight number" },
                        "arrival_airport": { "type": "string", "description": "Arrival airport name" },
                        "arrival_terminal": { "type": "string", "description": "Detected terminal (e.g. 'T5', 'North')" },
                        "arrival_time": { "type": "string", "description": "Scheduled arrival time HH:MM" },
                        "departure_airport": { "type": "string", "description": "Departure airport IATA code" }
                      }
                    },
                    "message": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Booking failed or missing fields",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/lookup-booking": {
      "post": {
        "operationId": "lookupBooking",
        "summary": "Look up a booking by reference",
        "description": "Retrieves full booking details for a given booking reference. Returns pickup/dropoff locations, date, time, passengers, price, status, assigned driver details, and a manage booking link.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["booking_ref"],
                "properties": {
                  "booking_ref": {
                    "type": "string",
                    "description": "Booking reference (e.g. 'APL-123456' or '955125')",
                    "examples": ["APL-123456", "955125"]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Booking details found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "booking_ref": { "type": "string", "description": "Booking reference number" },
                    "pickup": { "type": "string", "description": "Pickup location name" },
                    "dropoff": { "type": "string", "description": "Dropoff location name" },
                    "date": { "type": "string", "description": "Transfer date (YYYY-MM-DD)" },
                    "time": { "type": "string", "description": "Pickup time (HH:MM)" },
                    "passengers": { "type": "integer", "description": "Number of passengers" },
                    "price": { "type": "number", "description": "Price in GBP" },
                    "status": { "type": "string", "description": "Booking status (e.g. 'confirmed', 'completed', 'cancelled')" },
                    "driver": {
                      "type": "object",
                      "nullable": true,
                      "description": "Assigned driver details (null if not yet assigned)",
                      "properties": {
                        "name": { "type": "string", "description": "Driver full name" },
                        "phone": { "type": "string", "description": "Driver phone number" },
                        "vehicle": { "type": "string", "description": "Vehicle description" }
                      }
                    },
                    "manage_booking_url": { "type": "string", "description": "Link to manage booking, track driver, and pay online" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid booking reference",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string" }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Booking not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/amend-booking": {
      "post": {
        "operationId": "amendBooking",
        "summary": "Amend an existing booking",
        "description": "Amends one or more fields on an existing booking. Only the fields included in the 'changes' object will be updated; all others remain unchanged.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["booking_ref", "changes"],
                "properties": {
                  "booking_ref": {
                    "type": "string",
                    "description": "Booking reference (e.g. 'APL-123456' or '955125')",
                    "examples": ["APL-123456", "955125"]
                  },
                  "changes": {
                    "type": "object",
                    "description": "Fields to amend — only include the fields that need changing",
                    "properties": {
                      "passenger_name": { "type": "string", "description": "Updated passenger full name" },
                      "passenger_phone": { "type": "string", "description": "Updated phone number with country code" },
                      "flight_number": { "type": "string", "description": "Updated flight number (e.g. 'BA2534')" },
                      "special_requests": { "type": "string", "description": "Updated special requirements" },
                      "transfer_date": { "type": "string", "description": "Updated transfer date (YYYY-MM-DD)" },
                      "transfer_time": { "type": "string", "description": "Updated pickup time (HH:MM, 24-hour)" },
                      "passengers": { "type": "integer", "description": "Updated number of passengers" }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Booking amended successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean", "description": "Always true on success" },
                    "booking_ref": { "type": "string", "description": "Booking reference number" },
                    "amended_fields": {
                      "type": "array",
                      "items": { "type": "string" },
                      "description": "List of field names that were amended (e.g. ['passenger_name', 'transfer_time'])"
                    },
                    "message": { "type": "string", "description": "Confirmation message" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, missing fields, or no changes provided",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string" }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Booking not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/cancel-booking": {
      "post": {
        "operationId": "cancelBooking",
        "summary": "Cancel a booking (free cancellation 12+ hours before pickup)",
        "description": "Cancels an existing booking. Free cancellation 12 or more hours before the scheduled pickup time — no fee, full refund. Cancellations within 12 hours are rejected by the API; the customer must call the office.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["booking_ref"],
                "properties": {
                  "booking_ref": {
                    "type": "string",
                    "description": "Booking reference (e.g. 'APL-123456' or '955125')",
                    "examples": ["APL-123456", "955125"]
                  },
                  "reason": {
                    "type": "string",
                    "description": "Optional reason for cancellation"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Booking cancelled successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean", "description": "Always true on success" },
                    "booking_ref": { "type": "string", "description": "Booking reference number" },
                    "status": { "type": "string", "description": "New booking status — 'cancelled'" },
                    "hours_before_pickup": { "type": "number", "description": "Hours remaining before the scheduled pickup time" },
                    "admin_fee": { "type": "string", "description": "Always '£0' — cancellations through this API carry no fee" },
                    "message": { "type": "string", "description": "Confirmation message" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or cancellation not allowed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string" }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Booking not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/track": {
      "post": {
        "operationId": "trackDriver",
        "summary": "Track assigned driver live",
        "description": "Returns real-time GPS position, ETA, and driver/vehicle details for a booking with an assigned driver. Use this to show the customer where their driver is and when they will arrive.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["booking_ref"],
                "properties": {
                  "booking_ref": {
                    "type": "string",
                    "description": "Booking reference (e.g. 'APL-123456' or '955125')",
                    "examples": ["APL-123456", "955125"]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Live tracking data for the assigned driver",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "booking_ref": { "type": "string", "description": "Booking reference number" },
                    "driver": {
                      "type": "object",
                      "description": "Assigned driver and vehicle details",
                      "properties": {
                        "id": { "type": "integer", "description": "Driver ID" },
                        "name": { "type": "string", "description": "Driver full name" },
                        "first_name": { "type": "string", "description": "Driver first name" },
                        "last_name": { "type": "string", "description": "Driver last name" },
                        "phone": { "type": "string", "description": "Driver phone number" },
                        "photo_url": { "type": "string", "description": "Driver photo URL" },
                        "pco_licence": { "type": "string", "description": "PCO licence number" },
                        "rating": { "type": "number", "description": "Driver rating (1-5)" },
                        "vehicle": { "type": "string", "description": "Full vehicle description" },
                        "vehicle_make": { "type": "string", "description": "Vehicle make (e.g. 'Mercedes')" },
                        "vehicle_model": { "type": "string", "description": "Vehicle model (e.g. 'V-Class')" },
                        "vehicle_colour": { "type": "string", "description": "Vehicle colour" },
                        "vehicle_reg": { "type": "string", "description": "Vehicle registration plate" },
                        "vehicle_pax": { "type": "integer", "description": "Vehicle passenger capacity" }
                      }
                    },
                    "driver_status": { "type": "string", "description": "Driver status (e.g. 'on_way', 'arrived', 'in_progress', 'completed')" },
                    "gps_status": { "type": "string", "description": "GPS signal status (e.g. 'live', 'stale', 'unavailable')" },
                    "position": {
                      "type": "object",
                      "nullable": true,
                      "description": "Current GPS position (null if GPS unavailable)",
                      "properties": {
                        "latitude": { "type": "number", "description": "Latitude in decimal degrees" },
                        "longitude": { "type": "number", "description": "Longitude in decimal degrees" },
                        "heading": { "type": "number", "description": "Heading in degrees (0-360)" },
                        "speed_mph": { "type": "number", "description": "Speed in miles per hour" },
                        "interpolated": { "type": "boolean", "description": "Whether the position is interpolated between GPS updates" },
                        "tunnel": { "type": "boolean", "description": "Whether the driver is in a tunnel (GPS signal lost)" },
                        "tunnel_progress": { "type": "number", "description": "Estimated progress through tunnel (0-1), only present when tunnel is true" }
                      }
                    },
                    "position_source": { "type": "string", "description": "Source of position data (e.g. 'gps', 'interpolated', 'last_known')" },
                    "eta_minutes": { "type": "number", "nullable": true, "description": "Estimated minutes until driver arrives at pickup (null if not calculable)" },
                    "pickup_time": { "type": "string", "nullable": true, "description": "Actual pickup time if driver has collected passenger (ISO 8601)" },
                    "dropoff_time": { "type": "string", "nullable": true, "description": "Actual dropoff time if journey completed (ISO 8601)" },
                    "tracked_at": { "type": "string", "description": "Timestamp of this tracking snapshot (ISO 8601)" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or no driver assigned yet",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string" }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Booking not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {},
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "API key for authentication. Register at https://mcp.airport-pickups-london.com/a2a/register to get one."
      }
    }
  },
  "security": [
    { "ApiKeyAuth": [] }
  ]
}
