{
  "openapi": "3.0.3",
  "info": {
    "title": "Fly & Drive Connect API",
    "version": "1.0.0",
    "description": "REST API for third-party integrators to search drive inventory (cars and camper vans)\nand create bookings on the Fly & Drive OTA.\n\n## Base URL\n\nCanonical API host:\n\n`https://api.fly-and-drive.com/v1`\n\nLegacy (supported): `https://developer.fly-and-drive.com/api/connect/v1`\n\nThe embed widget loader lives at `https://www.fly-and-drive.com/connect/widget.js`\nand reads theme config from `https://api.fly-and-drive.com/widget-config`.\n\n## Authentication\n\nSend your API key using **one** of:\n\n- `Authorization: Bearer fd_connect_test_...` (sandbox)\n- `Authorization: Bearer fd_connect_live_...` (production)\n- Header `X-Fly-Drive-Partner-Key: fd_connect_...`\n- Query parameter `?key=fd_connect_...` (widget config only)\n\nKey prefix determines environment — not the hostname.\n\n## IP allowlist (optional)\n\nRestrict server-side API usage to specific egress IPs or CIDR ranges per key.\nConfigure in the [developer dashboard](https://developer.fly-and-drive.com/developer/dashboard?section=keys)\nor via `PUT /api/partner/connect/keys/allowlist` (dashboard session auth).\n\n- **Empty allowlist** (default): all client IPs allowed.\n- **Non-empty allowlist**: only listed IPv4/IPv6 addresses or CIDR blocks may call Connect v1 routes.\n- **Widget config** (`GET /widget-config`) is **not** IP-restricted — it is loaded from end-user browsers.\n- Denied requests return **HTTP 403** with `{ \"error\": \"...\", \"clientIp\": \"<detected-ip>\" }` to aid debugging.\n\nUp to **32** entries per key (e.g. `203.0.113.10`, `198.51.100.0/24`, `2001:db8::/32`).\n\n## Environments\n\n| Key prefix | Inventory | Payment |\n|------------|-----------|---------|\n| `fd_connect_test_` | Sandbox partners only (`is_sandbox`) | Internal wallet |\n| `fd_connect_live_` | Production fleet | Stripe Checkout |\n\nTest keys **cannot** book live inventory (HTTP 403). Live keys **cannot** book sandbox inventory.\n\n## Rate limiting\n\n**10 requests per second** per Connect partner. Every response includes:\n\n- `X-RateLimit-Limit: 10`\n- `X-RateLimit-Remaining: <n>`\n- `X-RateLimit-Reset: <unix-seconds>` (when using Upstash)\n- `Retry-After: <seconds>` on HTTP 429\n\nExceeding the limit returns **HTTP 429** with `Retry-After` (seconds).\n\nLimits are enforced globally via **Upstash Redis** when `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` are configured (required in production on Vercel). Check `GET /` discovery — `rateLimit.distributed` should be `true`.\n\n## Idempotency (bookings)\n\nPass header `Idempotency-Key` (1–256 characters) on `POST /bookings`.\n\n- **Replay:** Same key within **24 hours** returns the stored response with `idempotentReplay: true`.\n- **Concurrent duplicate:** Returns **HTTP 409** while the first request is in progress.\n- **Failed booking:** The key is released; you may retry with the same key.\n\n## Webhooks (outbound)\n\nRegister test and live URLs separately in the [developer dashboard](https://developer.fly-and-drive.com/developer/dashboard).\n\n| Event | When |\n|-------|------|\n| `booking.created` | Booking submitted, pending partner approval |\n| `booking.confirmed` | Partner accepted |\n| `booking.rejected` | Partner rejected or approval timeout |\n| `booking.cancelled` | Customer cancelled via mobile app |\n\nDeliveries are signed: `Fly-Drive-Signature: t={unix},v1={hmac_sha256_hex}` over `{timestamp}.{raw_body}`.\nFailed deliveries retry automatically (3 attempts, 60s then 5min backoff).\n\nMachine-readable spec: `GET https://api.fly-and-drive.com/openapi`\n\nPostman: import OpenAPI from the URL above, or use the curated collection at\n`GET https://api.fly-and-drive.com/postman/collection.json` with sandbox environment\n`GET https://api.fly-and-drive.com/postman/environment/sandbox.json`.\n"
  },
  "servers": [
    {
      "url": "https://api.fly-and-drive.com/v1",
      "description": "Connect API v1 (canonical)"
    },
    {
      "url": "https://developer.fly-and-drive.com/api/connect/v1",
      "description": "Legacy (same routes, portal origin)"
    }
  ],
  "tags": [
    {
      "name": "Search",
      "description": "Vehicle inventory search"
    },
    {
      "name": "Bookings",
      "description": "Create and retrieve bookings"
    },
    {
      "name": "Widget",
      "description": "Embed widget theme (served at /widget-config on api host)"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "paths": {
    "/search/drive": {
      "get": {
        "summary": "Search vehicle offers at an airport",
        "operationId": "searchDrive",
        "tags": [
          "Search"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AirportQuery"
          },
          {
            "$ref": "#/components/parameters/FromDateQuery"
          },
          {
            "$ref": "#/components/parameters/ToDateQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "Offer list",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OfferList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Missing `search:drive` scope, or request IP not on the key allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/bookings": {
      "post": {
        "summary": "Create a drive booking from a selected offer",
        "operationId": "createBooking",
        "tags": [
          "Bookings"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBookingRequest"
              },
              "example": {
                "offerId": "partner-uuid-agency-uuid-0",
                "airportIata": "SFO",
                "pickupDate": "2026-09-01",
                "returnDate": "2026-09-08",
                "pickupTime": "10:00",
                "returnTime": "10:00",
                "customer": {
                  "firstName": "Jane",
                  "lastName": "Doe",
                  "email": "jane@example.com",
                  "phone": "+14155550100"
                },
                "anytimeFlex": {
                  "selected": false
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Booking created (or demo mode when Stripe is unavailable)",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingCreated"
                },
                "examples": {
                  "pending": {
                    "summary": "Standard response (test or live)",
                    "value": {
                      "object": "booking",
                      "environment": "test",
                      "bookingId": "550e8400-e29b-41d4-a716-446655440000",
                      "reservationNumber": "FD-2026-ABC123",
                      "status": "pending_partner_approval",
                      "paymentUrl": null,
                      "paidWithPay": true,
                      "createdAt": "2026-09-01T10:00:00.000Z"
                    }
                  },
                  "idempotent_replay": {
                    "summary": "Idempotency replay",
                    "value": {
                      "object": "booking",
                      "environment": "test",
                      "bookingId": "550e8400-e29b-41d4-a716-446655440000",
                      "reservationNumber": "FD-2026-ABC123",
                      "status": "pending_partner_approval",
                      "idempotentReplay": true
                    }
                  },
                  "demo": {
                    "summary": "Stripe not configured (non-production only)",
                    "value": {
                      "object": "booking",
                      "environment": "test",
                      "status": "demo",
                      "message": "Stripe not configured — demo mode only"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Missing scope, live key required, sandbox/production inventory mismatch, or request IP not on the key allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "sandbox_isolation": {
                    "value": {
                      "error": "Test API keys cannot book production inventory. Use offers returned from a test key search."
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Idempotency-Key already in use by an in-flight request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "A request with this Idempotency-Key is already in progress. Retry shortly."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/bookings/{id}": {
      "get": {
        "summary": "Retrieve a booking created via Connect API",
        "operationId": "getBooking",
        "tags": [
          "Bookings"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Booking UUID returned by POST /bookings",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Booking details (scoped to your Connect partner)",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Missing `book:drive` scope, or request IP not on the key allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Booking not found or not owned by this integrator",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/bookings/{id}/voucher": {
      "get": {
        "summary": "Download PDF rental voucher for a confirmed booking",
        "operationId": "getBookingVoucher",
        "tags": [
          "Bookings"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Booking UUID returned by POST /bookings",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PDF voucher (application/pdf)",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "Content-Disposition": {
                "schema": {
                  "type": "string",
                  "example": "attachment; filename=\"FlyDrive-FD-2026-ABC123.pdf\""
                }
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Missing `voucher` scope, or request IP not on the key allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Booking not found or not owned by this integrator",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Booking not confirmed yet (voucher unavailable)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/widget-config": {
      "get": {
        "summary": "Load embed widget theme for an API key",
        "description": "Returns accent colors, logo, language, and allowed domains for the\n[Connect embed widget](https://www.fly-and-drive.com/connect/widget.js).\n\n**Note:** Canonical host: `GET https://api.fly-and-drive.com/widget-config?key=fd_connect_test_...`\n",
        "operationId": "getWidgetConfig",
        "tags": [
          "Widget"
        ],
        "servers": [
          {
            "url": "https://api.fly-and-drive.com",
            "description": "Widget config (api host, no /v1 suffix)"
          }
        ],
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyQuery": []
          }
        ],
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": false,
            "description": "API key (alternative to Authorization header — used by widget.js)",
            "schema": {
              "type": "string",
              "example": "fd_connect_test_abc123"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Widget theme",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WidgetConfigResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API secret key. Prefix determines environment:\n`fd_connect_test_*` (sandbox) or `fd_connect_live_*` (production).\n"
      },
      "ApiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "key",
        "description": "API key as query parameter (widget loader only)"
      }
    },
    "parameters": {
      "AirportQuery": {
        "name": "airport",
        "in": "query",
        "required": true,
        "description": "IATA airport code (aliases `pickup` also accepted)",
        "schema": {
          "type": "string",
          "example": "SFO"
        }
      },
      "FromDateQuery": {
        "name": "from",
        "in": "query",
        "required": true,
        "description": "Pickup date YYYY-MM-DD (alias `pickup`)",
        "schema": {
          "type": "string",
          "format": "date",
          "example": "2026-09-01"
        }
      },
      "ToDateQuery": {
        "name": "to",
        "in": "query",
        "required": true,
        "description": "Return date YYYY-MM-DD (alias `return`)",
        "schema": {
          "type": "string",
          "format": "date",
          "example": "2026-09-08"
        }
      },
      "IdempotencyKeyHeader": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Unique key (1–256 chars) for safe booking retries. Stored 24 hours.\nReplays return the original response. Concurrent duplicates return HTTP 409.\n",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 256,
          "example": "booking-req-7f3a9c2e-2026-09-01"
        }
      }
    },
    "headers": {
      "XRateLimitLimit": {
        "description": "Maximum requests per second for this partner",
        "schema": {
          "type": "integer",
          "example": 10
        }
      },
      "XRateLimitRemaining": {
        "description": "Remaining requests in the current 1-second window",
        "schema": {
          "type": "integer",
          "example": 9
        }
      },
      "XRateLimitReset": {
        "description": "Unix timestamp (seconds) when the rate limit window resets",
        "schema": {
          "type": "integer",
          "example": 1723046401
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying (HTTP 429 only)",
        "schema": {
          "type": "integer",
          "example": 1
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Invalid or missing API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Unauthorized · provide Authorization: Bearer fd_connect_... or X-Fly-Drive-Partner-Key"
            }
          }
        }
      },
      "BadRequest": {
        "description": "Validation error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded (10 req/s per partner)",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/XRateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/XRateLimitRemaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Too Many Requests. Please slow down."
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Database or upstream service unavailable",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "clientIp": {
            "type": "string",
            "description": "Detected client IP (included on IP allowlist 403 responses)"
          }
        }
      },
      "OfferList": {
        "type": "object",
        "required": [
          "object",
          "environment",
          "count",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "environment": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "partnerId": {
            "type": "string",
            "format": "uuid"
          },
          "commissionBps": {
            "type": "integer",
            "example": 800
          },
          "count": {
            "type": "integer"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Offer"
            }
          }
        }
      },
      "Offer": {
        "type": "object",
        "required": [
          "id",
          "vehicle",
          "supplier",
          "pricing",
          "location"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Pass as `offerId` when creating a booking",
            "example": "partner-uuid-agency-uuid-0"
          },
          "vehicle": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "category": {
                "type": "string"
              },
              "transmission": {
                "type": "string"
              },
              "seats": {
                "type": "integer"
              },
              "bags": {
                "type": "integer"
              },
              "fuelType": {
                "type": "string"
              },
              "image": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "supplier": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "rating": {
                "type": "number"
              },
              "reviewCount": {
                "type": "integer"
              }
            }
          },
          "pricing": {
            "type": "object",
            "properties": {
              "currency": {
                "type": "string",
                "example": "EUR"
              },
              "total": {
                "type": "number"
              },
              "payNow": {
                "type": "number"
              },
              "payAtCounter": {
                "type": "number"
              },
              "deposit": {
                "type": "number"
              },
              "pricePerDay": {
                "type": "number"
              }
            }
          },
          "location": {
            "type": "object",
            "properties": {
              "pickup": {
                "type": "string"
              },
              "type": {
                "type": "string"
              },
              "distanceKm": {
                "type": "number"
              }
            }
          }
        }
      },
      "CreateBookingRequest": {
        "type": "object",
        "required": [
          "offerId",
          "airportIata",
          "pickupDate",
          "customer"
        ],
        "properties": {
          "offerId": {
            "type": "string",
            "description": "Offer `id` from GET /search/drive"
          },
          "airportIata": {
            "type": "string",
            "example": "SFO"
          },
          "pickupDate": {
            "type": "string",
            "format": "date"
          },
          "returnDate": {
            "type": "string",
            "format": "date",
            "description": "Alias for dropoffDate (one of returnDate or dropoffDate required)"
          },
          "dropoffDate": {
            "type": "string",
            "format": "date"
          },
          "pickupTime": {
            "type": "string",
            "example": "10:00",
            "default": "10:00"
          },
          "returnTime": {
            "type": "string",
            "example": "10:00",
            "default": "10:00"
          },
          "customer": {
            "type": "object",
            "required": [
              "email"
            ],
            "properties": {
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              },
              "phone": {
                "type": "string",
                "example": "+33612345678"
              }
            }
          },
          "addOns": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Option keys to enable (maps to selectedOptions)"
          },
          "selectedOptions": {
            "type": "object",
            "additionalProperties": {
              "type": "boolean"
            }
          },
          "anytimeFlex": {
            "type": "object",
            "description": "Optional Anytime Flex upsell. Defaults to { selected: false } if omitted.",
            "properties": {
              "selected": {
                "type": "boolean",
                "default": false
              }
            }
          }
        }
      },
      "BookingCreated": {
        "type": "object",
        "required": [
          "object",
          "environment",
          "status"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "booking"
            ]
          },
          "environment": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "bookingId": {
            "type": "string",
            "format": "uuid",
            "description": "Omitted in demo mode"
          },
          "reservationNumber": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "pending_partner_approval",
              "pending_payment",
              "demo"
            ],
            "description": "pending_partner_approval after sandbox auto-pay or Stripe auth; pending_payment when paymentUrl is returned (Live)"
          },
          "paymentUrl": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Stripe Checkout URL (live keys). Null when paid via wallet (test keys)."
          },
          "paidWithPay": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "idempotentReplay": {
            "type": "boolean",
            "description": "True when this response was replayed from a prior Idempotency-Key"
          },
          "message": {
            "type": "string",
            "description": "Present when status is demo"
          }
        }
      },
      "BookingDetail": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "booking"
            ]
          },
          "environment": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "bookingId": {
            "type": "string",
            "format": "uuid"
          },
          "reservationNumber": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "description": "e.g. pending_partner_approval, confirmed, cancelled"
          },
          "airportIata": {
            "type": "string"
          },
          "pickupAt": {
            "type": "string",
            "format": "date-time"
          },
          "dropoffAt": {
            "type": "string",
            "format": "date-time"
          },
          "customer": {
            "type": "object",
            "properties": {
              "email": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "voucherToken": {
            "type": "string",
            "nullable": true,
            "description": "Available when status is confirmed, active, or completed"
          },
          "vehicle": {
            "type": "object",
            "nullable": true,
            "properties": {
              "name": {
                "type": "string"
              }
            }
          },
          "location": {
            "type": "object",
            "nullable": true,
            "properties": {
              "city": {
                "type": "string"
              }
            }
          }
        }
      },
      "WidgetConfigResponse": {
        "type": "object",
        "properties": {
          "partnerId": {
            "type": "string",
            "format": "uuid"
          },
          "environment": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "theme": {
            "type": "object",
            "properties": {
              "accentColor": {
                "type": "string",
                "example": "#00E699"
              },
              "backgroundColor": {
                "type": "string",
                "example": "#0a0a0a"
              },
              "lang": {
                "type": "string",
                "example": "en"
              },
              "logoUrl": {
                "type": "string",
                "format": "uri",
                "nullable": true
              },
              "allowedDomains": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "searchUrl": {
            "type": "string",
            "format": "uri",
            "description": "Default marketplace search URL opened by the widget"
          }
        }
      },
      "WebhookEventPayload": {
        "type": "object",
        "description": "Outbound webhook POST body (not an inbound API endpoint).\nHeader `Fly-Drive-Event` mirrors `type`.\n",
        "properties": {
          "id": {
            "type": "string",
            "example": "evt_a1b2c3d4e5f6"
          },
          "object": {
            "type": "string",
            "enum": [
              "event"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "booking.created",
              "booking.confirmed",
              "booking.rejected",
              "booking.cancelled"
            ]
          },
          "created": {
            "type": "integer",
            "description": "Unix timestamp"
          },
          "data": {
            "type": "object",
            "properties": {
              "bookingId": {
                "type": "string",
                "format": "uuid"
              },
              "reservationNumber": {
                "type": "string",
                "nullable": true
              },
              "status": {
                "type": "string"
              },
              "environment": {
                "type": "string",
                "enum": [
                  "live",
                  "test"
                ]
              },
              "airportIata": {
                "type": "string"
              },
              "pickupAt": {
                "type": "string",
                "format": "date-time"
              },
              "dropoffAt": {
                "type": "string",
                "format": "date-time"
              },
              "cancelledBy": {
                "type": "string",
                "description": "Present on booking.cancelled (e.g. customer)"
              },
              "refundAmount": {
                "type": "number",
                "description": "Present on booking.cancelled"
              },
              "penaltyAmount": {
                "type": "number"
              },
              "reason": {
                "type": "string",
                "description": "Present on booking.rejected (reject or timeout)"
              }
            }
          }
        }
      }
    }
  }
}
