{
  "openapi": "3.1.0",
  "info": {
    "title": "SHAFFL Partner Distribution API",
    "version": "1.1.0",
    "description": "Search and book Egyptian intercity coach inventory. Prices are NET to your account; you set retail and keep the spread. Bookings settle from a prepaid wallet.\n\nMoney is always an integer in minor units (piastres): 47000 = EGP 470.00.\n\nUnknown request fields are REJECTED, not ignored — a typo costs you one failed call rather than one wrong booking.\n\nThere is no cancellation or refund endpoint by design. Disruptions are handled through your account manager.",
    "contact": {
      "name": "SHAFFL Partner Support",
      "email": "partners@shaffl.com"
    }
  },
  "servers": [
    {
      "url": "https://api.shaffl.com/partner/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Authentication"
    },
    {
      "name": "Reference",
      "description": "Cities and operators. Cache these."
    },
    {
      "name": "Inventory",
      "description": "Search and seat maps."
    },
    {
      "name": "Bookings",
      "description": "Holds and bookings."
    },
    {
      "name": "Wallet"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem detail. Key off `code`; it is stable.",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable error code.",
            "examples": [
              "offer_expired"
            ]
          },
          "detail": {
            "type": "string"
          },
          "request_id": {
            "type": "string",
            "description": "Quote this to support."
          },
          "errors": {
            "type": "array",
            "description": "Present on 422. One entry per invalid field.",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        },
        "required": [
          "title",
          "status",
          "code"
        ]
      },
      "PageMeta": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_before": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Pass as `?before=` to fetch the next page. Null on the last page."
          }
        }
      },
      "City": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque and stable. This is what /search expects — never a name.",
            "examples": [
              "cty_kcr_rp4Lnl05dGN4NWhnuI4ag3m7IObXgZGTUxqZ"
            ]
          },
          "name": {
            "type": "string",
            "examples": [
              "Cairo"
            ]
          },
          "name_ar": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "القاهرة"
            ]
          },
          "country": {
            "type": "string",
            "examples": [
              "EG"
            ]
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "Operator": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "opr_9PZPBYZN_YVth"
            ]
          },
          "name": {
            "type": "string",
            "examples": [
              "SuperJet"
            ]
          },
          "name_ar": {
            "type": [
              "string",
              "null"
            ]
          },
          "logo_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Served from api.shaffl.com. Null when we hold no logo — render your own placeholder."
          }
        },
        "required": [
          "id",
          "name"
        ]
      },
      "Offer": {
        "type": "object",
        "description": "A priced quote for one departure, issued to your account and valid for 60 minutes. Not a permanent identifier for a departure.",
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "off_kcqaogpKJnmLZ_dl-We1Hr7Gy3IrCNoOi5G8VyQ"
            ]
          },
          "origin": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "examples": [
                  "Cairo"
                ]
              },
              "station": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The boarding point. Show this — a city has several.",
                "examples": [
                  "6 October - El Hussary"
                ]
              }
            }
          },
          "destination": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "examples": [
                  "Hurghada"
                ]
              },
              "station": {
                "type": [
                  "string",
                  "null"
                ],
                "examples": [
                  "El Nasr Street"
                ]
              }
            }
          },
          "departure_at": {
            "type": "string",
            "format": "date-time"
          },
          "arrival_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "duration_minutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Null when the operator publishes no reliable arrival. Hide it rather than showing 0.",
            "examples": [
              509
            ]
          },
          "operator": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "name_ar": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "seat_class": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Comfort"
            ]
          },
          "seats_available": {
            "type": [
              "integer",
              "null"
            ],
            "examples": [
              40
            ]
          },
          "net_price": {
            "type": "object",
            "description": "Your price per seat. Multiply by passengers for the total.",
            "properties": {
              "amount_minor": {
                "type": "integer",
                "description": "Integer minor units (piastres). 47000 = EGP 470.00. Never a decimal.",
                "examples": [
                  47000
                ]
              },
              "currency": {
                "type": "string",
                "examples": [
                  "EGP"
                ]
              }
            },
            "required": [
              "amount_minor",
              "currency"
            ]
          },
          "amenities": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "usb",
                "wifi",
                "toilet",
                "air_conditioning",
                "entertainment",
                "refreshments",
                "blanket",
                "extra_legroom",
                "power_socket"
              ]
            }
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "origin",
          "destination",
          "departure_at",
          "net_price",
          "expires_at"
        ]
      },
      "Seat": {
        "type": "object",
        "properties": {
          "number": {
            "type": "string",
            "description": "Send this as passengers[].seat when booking.",
            "examples": [
              "3"
            ]
          },
          "available": {
            "type": "boolean"
          },
          "price": {
            "type": "object",
            "description": "Price for this seat.",
            "properties": {
              "amount_minor": {
                "type": "integer",
                "description": "Integer minor units (piastres). 47000 = EGP 470.00. Never a decimal.",
                "examples": [
                  47000
                ]
              },
              "currency": {
                "type": "string",
                "examples": [
                  "EGP"
                ]
              }
            },
            "required": [
              "amount_minor",
              "currency"
            ]
          },
          "class": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Comfort"
            ]
          },
          "features": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "usb"
              ]
            ]
          },
          "accessible": {
            "type": "boolean",
            "description": "Wheelchair-accessible."
          },
          "premium": {
            "type": "boolean"
          },
          "position": {
            "type": [
              "object",
              "null"
            ],
            "description": "Grid position, matching a SEAT cell in seat_map.",
            "properties": {
              "row": {
                "type": "integer"
              },
              "column": {
                "type": "integer"
              }
            }
          }
        },
        "required": [
          "number",
          "available",
          "price"
        ]
      },
      "SeatMap": {
        "type": [
          "object",
          "null"
        ],
        "description": "Null when the operator assigns seats at boarding.",
        "properties": {
          "layout": {
            "type": "string",
            "examples": [
              "coach_4across_44"
            ]
          },
          "deck_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "rows": {
            "type": "integer",
            "examples": [
              13
            ]
          },
          "columns": {
            "type": "integer",
            "examples": [
              5
            ]
          },
          "seat_count": {
            "type": "integer",
            "examples": [
              44
            ]
          },
          "cells": {
            "type": "array",
            "description": "Lay these on a rows × columns grid.",
            "items": {
              "type": "object",
              "properties": {
                "row": {
                  "type": "integer"
                },
                "column": {
                  "type": "integer"
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "SEAT",
                    "AISLE",
                    "EMPTY",
                    "DRIVER",
                    "DOOR",
                    "STAIRS",
                    "WC",
                    "TABLE"
                  ]
                },
                "seat": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Set on SEAT cells. Look it up in seats[] by number."
                }
              }
            }
          }
        }
      },
      "OfferDetail": {
        "type": "object",
        "description": "Everything needed to draw a seat picker. Re-read live on every call.",
        "properties": {
          "id": {
            "type": "string"
          },
          "origin": {
            "type": "object",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "station": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "departs_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "destination": {
            "type": "object",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "station": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "arrives_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "duration_minutes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "operator": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "name_ar": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "vehicle": {
            "type": "object",
            "properties": {
              "capacity": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "label": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "seats_available": {
            "type": [
              "integer",
              "null"
            ]
          },
          "seat_selection": {
            "type": "boolean",
            "description": "False → book without seats; the operator assigns at boarding."
          },
          "net_price": {
            "type": "object",
            "description": "Your price per seat.",
            "properties": {
              "amount_minor": {
                "type": "integer",
                "description": "Integer minor units (piastres). 47000 = EGP 470.00. Never a decimal.",
                "examples": [
                  47000
                ]
              },
              "currency": {
                "type": "string",
                "examples": [
                  "EGP"
                ]
              }
            },
            "required": [
              "amount_minor",
              "currency"
            ]
          },
          "price_range": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "min_minor": {
                "type": "integer"
              },
              "max_minor": {
                "type": "integer"
              }
            }
          },
          "fare_classes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "price": {
                  "type": "object",
                  "description": "Price for this class.",
                  "properties": {
                    "amount_minor": {
                      "type": "integer",
                      "description": "Integer minor units (piastres). 47000 = EGP 470.00. Never a decimal.",
                      "examples": [
                        47000
                      ]
                    },
                    "currency": {
                      "type": "string",
                      "examples": [
                        "EGP"
                      ]
                    }
                  },
                  "required": [
                    "amount_minor",
                    "currency"
                  ]
                },
                "seats_available": {
                  "type": "integer"
                },
                "features": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "seats": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Seat"
            }
          },
          "seat_map": {
            "$ref": "#/components/schemas/SeatMap"
          },
          "amenities": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PassengerInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "full_name"
        ],
        "properties": {
          "full_name": {
            "type": "string",
            "description": "As it should appear on the ticket.",
            "examples": [
              "Mona Ali"
            ]
          },
          "seat": {
            "type": "string",
            "description": "Seat number from GET /offers/{id} → seats[].number. Omit to have a seat assigned. Some operators require one per passenger.",
            "examples": [
              "3"
            ]
          },
          "phone": {
            "type": "string",
            "examples": [
              "+201000000000"
            ]
          },
          "email": {
            "type": "string",
            "format": "email"
          }
        }
      },
      "Booking": {
        "type": "object",
        "description": "Carries the whole journey, so you can render a ticket from this alone.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Your permanent reference. Store it and quote it to support.",
            "examples": [
              "PB-MSOR8JZNDE2CA7"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "confirmed",
              "failed",
              "pending"
            ]
          },
          "origin": {
            "type": "object",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "station": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Print this — where they board."
              },
              "departs_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "destination": {
            "type": "object",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "station": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "arrives_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "duration_minutes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "operator": {
            "type": "object",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "name_ar": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "travel_date": {
            "type": "string",
            "format": "date"
          },
          "departure_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "arrival_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "seat_class": {
            "type": [
              "string",
              "null"
            ]
          },
          "seats": {
            "type": "integer"
          },
          "passengers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "full_name": {
                  "type": "string"
                },
                "seat": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Null when the operator assigns at boarding."
                },
                "phone": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "email": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "price": {
            "type": "object",
            "properties": {
              "per_seat_minor": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "total_minor": {
                "type": "integer"
              },
              "currency": {
                "type": "string"
              }
            }
          },
          "total_charged": {
            "type": "object",
            "description": "Total charged. Zero on a failed booking — it was credited back.",
            "properties": {
              "amount_minor": {
                "type": "integer",
                "description": "Integer minor units (piastres). 47000 = EGP 470.00. Never a decimal.",
                "examples": [
                  47000
                ]
              },
              "currency": {
                "type": "string",
                "examples": [
                  "EGP"
                ]
              }
            },
            "required": [
              "amount_minor",
              "currency"
            ]
          },
          "contact": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "failure": {
            "type": [
              "object",
              "null"
            ],
            "description": "Null on success. Tell your customer this rather than 'booking failed'.",
            "properties": {
              "code": {
                "type": "string"
              },
              "detail": {
                "type": "string"
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "confirmed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Later than created_at when a retry was involved."
          }
        },
        "required": [
          "id",
          "status",
          "seats",
          "passengers",
          "price"
        ]
      },
      "Hold": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "PH-MSOR8JBOC3CD7B"
            ]
          },
          "offer_id": {
            "type": "string"
          },
          "seats": {
            "type": "integer"
          },
          "seats_requested": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "total": {
            "type": "object",
            "description": "Total held. Nothing is charged until you book.",
            "properties": {
              "amount_minor": {
                "type": "integer",
                "description": "Integer minor units (piastres). 47000 = EGP 470.00. Never a decimal.",
                "examples": [
                  47000
                ]
              },
              "currency": {
                "type": "string",
                "examples": [
                  "EGP"
                ]
              }
            },
            "required": [
              "amount_minor",
              "currency"
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "held",
              "consumed",
              "released",
              "expired"
            ]
          }
        }
      },
      "Wallet": {
        "type": "object",
        "properties": {
          "balance": {
            "type": "object",
            "description": "Available to book with.",
            "properties": {
              "amount_minor": {
                "type": "integer",
                "description": "Integer minor units (piastres). 47000 = EGP 470.00. Never a decimal.",
                "examples": [
                  47000
                ]
              },
              "currency": {
                "type": "string",
                "examples": [
                  "EGP"
                ]
              }
            },
            "required": [
              "amount_minor",
              "currency"
            ]
          },
          "credit_limit": {
            "type": "object",
            "description": "How far below zero you may go. Zero means strict prepaid.",
            "properties": {
              "amount_minor": {
                "type": "integer",
                "description": "Integer minor units (piastres). 47000 = EGP 470.00. Never a decimal.",
                "examples": [
                  47000
                ]
              },
              "currency": {
                "type": "string",
                "examples": [
                  "EGP"
                ]
              }
            },
            "required": [
              "amount_minor",
              "currency"
            ]
          },
          "low_balance_threshold": {
            "type": "object",
            "description": "Fires the wallet.low_balance notification.",
            "properties": {
              "amount_minor": {
                "type": "integer",
                "description": "Integer minor units (piastres). 47000 = EGP 470.00. Never a decimal.",
                "examples": [
                  47000
                ]
              },
              "currency": {
                "type": "string",
                "examples": [
                  "EGP"
                ]
              }
            },
            "required": [
              "amount_minor",
              "currency"
            ]
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Transaction": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "TOPUP",
              "DEBIT",
              "REVERSAL",
              "ADJUSTMENT"
            ]
          },
          "amount_minor": {
            "type": "integer",
            "description": "Signed. Negative debits you, positive credits you."
          },
          "balance_after_minor": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "booking_ref": {
            "type": [
              "string",
              "null"
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    }
  },
  "paths": {
    "/oauth/token": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Exchange credentials for an access token",
        "description": "Tokens last one hour. There is no refresh token.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "client_id",
                  "client_secret"
                ],
                "additionalProperties": false,
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "client_credentials"
                    ]
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Access token",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "access_token": {
                      "type": "string"
                    },
                    "token_type": {
                      "type": "string",
                      "examples": [
                        "Bearer"
                      ]
                    },
                    "expires_in": {
                      "type": "integer",
                      "examples": [
                        3600
                      ]
                    },
                    "scope": {
                      "type": "string"
                    },
                    "environment": {
                      "type": "string",
                      "enum": [
                        "SANDBOX",
                        "LIVE"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or revoked credentials",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Missing client_id or client_secret",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/reference/cities": {
      "get": {
        "tags": [
          "Reference"
        ],
        "summary": "Cities we serve",
        "description": "Fetch once and cache. These ids are what /search expects — you cannot search by name. Refresh monthly to pick up new destinations.",
        "responses": {
          "200": {
            "description": "City list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/City"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/reference/operators": {
      "get": {
        "tags": [
          "Reference"
        ],
        "summary": "Coach operators",
        "responses": {
          "200": {
            "description": "Operator list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Operator"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/search": {
      "post": {
        "tags": [
          "Inventory"
        ],
        "summary": "Search availability",
        "description": "Returns priced offers, each valid for 60 minutes and tied to your account. An empty array means no coaches on that route and date — a normal answer, not an error.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "origin",
                  "destination",
                  "departure_date"
                ],
                "additionalProperties": false,
                "properties": {
                  "origin": {
                    "type": "string",
                    "description": "City id from /reference/cities.",
                    "examples": [
                      "cty_kcr_rp4Lnl05dGN4NWhnuI4ag3m7IObX"
                    ]
                  },
                  "destination": {
                    "type": "string"
                  },
                  "departure_date": {
                    "type": "string",
                    "format": "date",
                    "description": "Must not be in the past, or more than a year ahead.",
                    "examples": [
                      "2026-09-01"
                    ]
                  },
                  "passengers": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10,
                    "default": 1,
                    "description": "Filters out coaches without room for this many."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Offers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Offer"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "count": {
                          "type": "integer"
                        },
                        "currency": {
                          "type": "string"
                        },
                        "offer_ttl_seconds": {
                          "type": "integer",
                          "examples": [
                            3600
                          ]
                        },
                        "passengers": {
                          "type": "integer"
                        },
                        "departure_date": {
                          "type": "string",
                          "format": "date"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "origin or destination is not a city id",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid, missing or unknown field — see errors[]",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/offers/{id}": {
      "get": {
        "tags": [
          "Inventory"
        ],
        "summary": "Seat map and full detail",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Offer with seats and seat map",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OfferDetail"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Unknown offer, or one issued to another account",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Offer expired — search again",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/holds": {
      "post": {
        "tags": [
          "Bookings"
        ],
        "summary": "Hold a quote for 20 minutes without charging",
        "description": "Use one while your customer completes checkout. Nothing is charged, and confirming re-uses the exact price you held.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "offer_id"
                ],
                "additionalProperties": false,
                "properties": {
                  "offer_id": {
                    "type": "string"
                  },
                  "seats": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10,
                    "default": 1
                  },
                  "seats_requested": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Seat numbers to hold. Omit to have them assigned.",
                    "examples": [
                      [
                        "7",
                        "8"
                      ]
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Hold created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Hold"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "409": {
            "description": "Offer expired",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid or unknown field",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Bookings"
        ],
        "summary": "List your holds",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "held",
                "consumed",
                "released",
                "expired"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Cursor from meta.next_before on the previous page."
          }
        ],
        "responses": {
          "200": {
            "description": "Holds",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Hold"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PageMeta"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/holds/{reference}": {
      "get": {
        "tags": [
          "Bookings"
        ],
        "summary": "Retrieve a hold",
        "parameters": [
          {
            "name": "reference",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Hold",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Hold"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Bookings"
        ],
        "summary": "Release a hold early",
        "description": "Costs nothing and frees the seats.",
        "parameters": [
          {
            "name": "reference",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Released",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No active hold with that reference",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/bookings": {
      "post": {
        "tags": [
          "Bookings"
        ],
        "summary": "Create a booking",
        "description": "Charges your wallet and confirms the seats together. If fulfilment fails your wallet is credited back automatically before we answer, and the booking is returned as failed — you are never charged for a booking you did not get.\n\nRetry a timed-out request with the SAME Idempotency-Key to get the original response rather than a second booking.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "description": "Unique per booking attempt. Reuse the exact value on retry.",
            "examples": {
              "uuid": {
                "value": "6f1c9e2a-3b4d-4c5e-8a1b-2d3e4f5a6b7c"
              }
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "offer_id",
                  "passengers",
                  "contact"
                ],
                "additionalProperties": false,
                "properties": {
                  "offer_id": {
                    "type": "string",
                    "description": "An offer id (off_…) or a hold reference (PH-…)."
                  },
                  "passengers": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 10,
                    "items": {
                      "$ref": "#/components/schemas/PassengerInput"
                    }
                  },
                  "contact": {
                    "type": "object",
                    "required": [
                      "name"
                    ],
                    "additionalProperties": false,
                    "description": "Who we contact about a disruption.",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "email": {
                        "type": "string",
                        "format": "email"
                      },
                      "phone": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Booking confirmed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Booking"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "402": {
            "description": "Wallet balance too low — nothing was booked or charged",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Seats unavailable, rejected, hold expired, or a duplicate request in flight",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid request, unknown field, or Idempotency-Key reused with a different body",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Could not reach the operator — retry with the same Idempotency-Key",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Bookings"
        ],
        "summary": "List bookings",
        "description": "Cursor-paged. Unknown query parameters are rejected.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "confirmed",
                "failed",
                "pending"
              ]
            }
          },
          {
            "name": "travel_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Exactly this travel date."
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Travel date range start."
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Travel date range end."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Cursor from meta.next_before on the previous page."
          }
        ],
        "responses": {
          "200": {
            "description": "Bookings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Booking"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PageMeta"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/bookings/{reference}": {
      "get": {
        "tags": [
          "Bookings"
        ],
        "summary": "Retrieve a booking",
        "parameters": [
          {
            "name": "reference",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Booking",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Booking"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not found on your account",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/wallet": {
      "get": {
        "tags": [
          "Wallet"
        ],
        "summary": "Balance",
        "responses": {
          "200": {
            "description": "Wallet",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Wallet"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/wallet/transactions": {
      "get": {
        "tags": [
          "Wallet"
        ],
        "summary": "Statement lines",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Cursor from meta.next_before on the previous page."
          }
        ],
        "responses": {
          "200": {
            "description": "Transactions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Transaction"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PageMeta"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "Reference"
        ],
        "summary": "Service status",
        "security": [],
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "examples": [
                        "ok"
                      ]
                    },
                    "service": {
                      "type": "string"
                    },
                    "version": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}