{
  "openapi": "3.1.0",
  "info": {
    "title": "Fix Price Taxi To Airport API",
    "version": "2.0.0",
    "description": "Fixed-price taxi transfer booking API for Melbourne Airport (MEL) and Avalon Airport (AVV). AI-agent flow: (1) POST /api/agent/quote for an exact fare to any address, (2) POST /api/agent/booking with the traveller's name + phone to create a real booking. The fare is computed server-side from Victorian ESC regulated tariffs (tolls and GST included); agents cannot set their own price. Our team always phones the traveller to confirm before pickup. A YAML mirror is at /.well-known/openapi.yaml.",
    "contact": {
      "name": "Fix Price Taxi To Airport",
      "email": "bookings@fixpricetaxitoairport.com.au",
      "url": "https://www.fixpricetaxitoairport.com.au"
    },
    "termsOfService": "https://www.fixpricetaxitoairport.com.au/terms"
  },
  "servers": [
    { "url": "https://www.fixpricetaxitoairport.com.au", "description": "Production" }
  ],
  "paths": {
    "/api/agent/quote": {
      "post": {
        "operationId": "getAgentQuote",
        "summary": "Get an exact fare for any address (no personal info required)",
        "description": "Returns the authoritative fixed fare between an airport and any Melbourne/Geelong street address or suburb. Geocoding, routing and pricing are done server-side. Rate limit: 20/min per IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/AgentTripRequest" },
              "examples": {
                "byAddress": {
                  "summary": "To the airport from a street address",
                  "value": {
                    "airport": "MEL",
                    "direction": "to_airport",
                    "address": "Crown Melbourne, 8 Whiteman St, Southbank VIC",
                    "pickup_date": "2026-07-01",
                    "pickup_time": "10:00",
                    "vehicle_type": "sedan",
                    "passengers": 1
                  }
                },
                "convenienceShape": {
                  "summary": "Airport pickup using pickup_airport + dropoff_address",
                  "value": {
                    "pickup_airport": "Melbourne Airport",
                    "dropoff_address": "Crown Melbourne, 8 Whiteman St, Southbank VIC",
                    "pickup_datetime": "2026-07-01T10:00:00+10:00",
                    "vehicle_type": "sedan"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote generated",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AgentQuoteResponse" }
              }
            }
          },
          "400": {
            "description": "Missing or invalid fields",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AgentErrorResponse" }
              }
            }
          },
          "404": { "description": "Address could not be resolved" },
          "429": { "description": "Rate limit exceeded" }
        }
      }
    },
    "/api/agent/booking": {
      "post": {
        "operationId": "createAgentBooking",
        "summary": "Create a real taxi booking from a plain-language request",
        "description": "Creates a confirmed booking and returns a reference (TXI-XXXXXXXX). Requires customer_name and customer_phone so our team can phone the traveller to confirm before pickup. Every agent booking is annotated 'Booked via AI agent'. The fare is recomputed server-side. Rate limit: 5/min per IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/AgentBookingRequest" },
              "example": {
                "airport": "MEL",
                "direction": "from_airport",
                "address": "Crown Melbourne, 8 Whiteman St, Southbank VIC",
                "pickup_datetime": "2026-07-01T10:00:00+10:00",
                "vehicle_type": "sedan",
                "passengers": 1,
                "customer_name": "Jane Smith",
                "customer_phone": "0412345678",
                "customer_email": "jane@example.com",
                "flight_number": "QF401"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Booking received",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AgentBookingResponse" }
              }
            }
          },
          "400": {
            "description": "Missing or invalid fields",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AgentErrorResponse" }
              }
            }
          },
          "404": { "description": "Address could not be resolved" },
          "429": { "description": "Rate limit exceeded (max 5 bookings per minute)" },
          "503": { "description": "Booking system temporarily unavailable — ask traveller to call" }
        }
      }
    },
    "/api/fare": {
      "get": {
        "operationId": "getFare",
        "summary": "Look up the published fixed fare for a known suburb",
        "description": "Returns the published fixed sedan fare between a known suburb slug and an airport. For arbitrary street addresses use /api/agent/quote. Rate limit: 30/min per IP.",
        "parameters": [
          {
            "name": "suburb",
            "in": "query",
            "required": true,
            "description": "Suburb slug (URL-formatted, e.g. south-yarra)",
            "schema": { "type": "string" },
            "example": "south-yarra"
          },
          {
            "name": "airport",
            "in": "query",
            "required": true,
            "schema": { "$ref": "#/components/schemas/AirportCode" },
            "example": "MEL"
          }
        ],
        "responses": {
          "200": {
            "description": "Fare found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FareResponse" }
              }
            }
          },
          "400": { "description": "Missing or invalid parameters" },
          "404": { "description": "Suburb not found" },
          "429": { "description": "Rate limit exceeded" }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AirportCode": {
        "type": "string",
        "enum": ["MEL", "AVV"],
        "description": "MEL = Melbourne Airport (Tullamarine); AVV = Avalon Airport (Geelong)"
      },
      "VehicleType": {
        "type": "string",
        "enum": ["sedan", "suv", "maxi_cab"],
        "default": "sedan",
        "description": "sedan: 1-4 pax (base fare); suv: 1-6 pax (+$17.80); maxi_cab: 5-11 pax (+$17.80)"
      },
      "Direction": {
        "type": "string",
        "enum": ["to_airport", "from_airport"],
        "description": "to_airport: address -> airport; from_airport: airport -> address"
      },
      "AgentTripRequest": {
        "type": "object",
        "required": ["airport", "direction", "address"],
        "description": "Provide either pickup_date + pickup_time, or a single ISO pickup_datetime. The convenience shape (pickup_airport + dropoff_address, or pickup_address + dropoff_airport) is also accepted and infers airport/direction/address.",
        "properties": {
          "airport": { "$ref": "#/components/schemas/AirportCode" },
          "direction": { "$ref": "#/components/schemas/Direction" },
          "address": {
            "type": "string",
            "description": "Free-form street address or suburb (the non-airport endpoint)",
            "example": "Crown Melbourne, 8 Whiteman St, Southbank VIC"
          },
          "pickup_date": {
            "type": "string",
            "format": "date",
            "description": "YYYY-MM-DD, Melbourne time, today or future",
            "example": "2026-07-01"
          },
          "pickup_time": {
            "type": "string",
            "description": "24-hour HH:MM or 12-hour h:MM AM/PM (Melbourne time)",
            "example": "10:00"
          },
          "pickup_datetime": {
            "type": "string",
            "format": "date-time",
            "description": "Alternative to pickup_date + pickup_time",
            "example": "2026-07-01T10:00:00+10:00"
          },
          "vehicle_type": { "$ref": "#/components/schemas/VehicleType" },
          "passengers": { "type": "integer", "minimum": 1, "maximum": 11, "default": 1 },
          "luggage": { "type": "integer", "minimum": 0, "maximum": 20 }
        }
      },
      "AgentBookingRequest": {
        "allOf": [
          { "$ref": "#/components/schemas/AgentTripRequest" },
          {
            "type": "object",
            "required": ["customer_name", "customer_phone"],
            "properties": {
              "customer_name": { "type": "string", "maxLength": 100, "example": "Jane Smith" },
              "customer_phone": {
                "type": "string",
                "description": "e.g. 0412345678 or +61412345678",
                "example": "0412345678"
              },
              "customer_email": { "type": "string", "format": "email", "example": "jane@example.com" },
              "flight_number": { "type": "string", "maxLength": 20, "example": "QF401" },
              "special_requests": {
                "type": "string",
                "maxLength": 500,
                "description": "Extra notes (baby seat, wheelchair, etc.). 'Booked via AI agent' is added automatically."
              }
            }
          }
        ]
      },
      "FareBreakdown": {
        "type": "object",
        "description": "Line-item construction of the fare (all AUD)",
        "properties": {
          "flagfall": { "type": "number" },
          "distanceCost": { "type": "number" },
          "tolls": { "type": "number" },
          "airportFee": { "type": "number" },
          "highOccupancyFee": { "type": "number" },
          "highOccupancyRateApplied": { "type": "boolean", "description": "True when the Geelong regional high-occupancy rate card is applied (different base rates, not an additive fee)" },
          "cpvLevy": { "type": "number" },
          "bookingFee": { "type": "number" },
          "longDistanceDiscount": { "type": "number" },
          "longDistanceDiscountRate": { "type": "number", "description": "Effective discount rate applied (0, 0.07, or 0.14)" },
          "preBookingMinimumTopUp": { "type": "number", "description": "Top-up applied to reach the $40 pre-booking minimum fare" },
          "lateNightFee": { "type": "number" },
          "holidayFee": { "type": "number" }
        }
      },
      "AgentQuote": {
        "type": "object",
        "properties": {
          "fare_aud": { "type": "number", "example": 96 },
          "currency": { "type": "string", "example": "AUD" },
          "vehicle_type": { "$ref": "#/components/schemas/VehicleType" },
          "passengers": { "type": "integer", "example": 1 },
          "distance_km": { "type": "number", "example": 38.2 },
          "duration_minutes": { "type": "number", "example": 32 },
          "tariff": { "type": "string", "example": "day" },
          "tariff_label": { "type": "string", "example": "Tariff 1 'Day'" },
          "fare_zone": { "type": "string", "example": "melbourne_metro" },
          "breakdown": { "$ref": "#/components/schemas/FareBreakdown" },
          "tolls_aud": { "type": "number", "example": 0 },
          "airport": { "$ref": "#/components/schemas/AirportCode" },
          "airport_name": { "type": "string", "example": "Melbourne Airport" },
          "direction": { "$ref": "#/components/schemas/Direction" },
          "resolved_address": {
            "type": "string",
            "description": "The full address Google resolved — show this to the traveller to confirm",
            "example": "Crown Melbourne, 8 Whiteman St, Southbank VIC 3006, Australia"
          },
          "place_id": { "type": "string" },
          "geo": {
            "type": "object",
            "description": "Resolved coordinates of the address endpoint",
            "properties": {
              "lat": { "type": "number" },
              "lng": { "type": "number" }
            }
          },
          "pickup_datetime": { "type": "string", "format": "date-time" }
        }
      },
      "AgentQuoteResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "quote": { "$ref": "#/components/schemas/AgentQuote" },
          "disclaimer": { "type": "string" }
        }
      },
      "AgentBookingResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "booking_id": { "type": "string", "example": "TXI-ABC123-XYZ789" },
          "status": { "type": "string", "example": "pending_contact" },
          "fare_aud": { "type": "number", "example": 96 },
          "currency": { "type": "string", "example": "AUD" },
          "vehicle_type": { "$ref": "#/components/schemas/VehicleType" },
          "pickup_datetime": { "type": "string", "format": "date-time" },
          "resolved_address": { "type": "string" },
          "message": { "type": "string" }
        }
      },
      "AgentErrorResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": false },
          "error": {
            "type": "string",
            "description": "Machine code: missing_fields, invalid, past_date, not_found, upstream, unavailable, rate_limited, server_error",
            "example": "missing_fields"
          },
          "message": { "type": "string" },
          "missing_fields": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Present when error is missing_fields — the fields to ask the traveller for"
          }
        }
      },
      "FareResponse": {
        "type": "object",
        "required": ["suburb_name", "airport_name", "distance_km", "travel_time_min", "fare_aud"],
        "properties": {
          "suburb_name": { "type": "string", "example": "South Yarra" },
          "airport_name": { "type": "string", "example": "Melbourne Airport" },
          "distance_km": { "type": "number", "example": 28.5 },
          "travel_time_min": { "type": "number", "example": 35 },
          "fare_aud": { "type": "number", "example": 82 }
        }
      }
    }
  }
}
