---
title: "Book a flight offer"
method: POST
path: "/api/v1/flights/book"
tags: ["Bookings"]
---

# Book a flight offer

`POST /api/v1/flights/book`

## Request

- Base URL: `https://letsfg.co/developers/api/v1`
- URL: `https://letsfg.co/developers/api/v1/api/v1/flights/book`
- Auth: API key in header `X-API-Key`

Book one offer from one of your own searches, on the Revolut method connected to this key.

The method is **held, not charged**, for the fare plus the payment markup. A LetsFG
booking agent then buys the ticket from the seller and the hold is captured **only against
a real airline PNR**. If the booking fails the hold is released and nothing is taken.

Answers `202` within seconds with a `booking_id`; the booking itself takes 4–11 minutes.
Poll `GET /flights/bookings/{booking_id}` until `terminal` is true. A completed booking
also resets your free-search allowance.

Retrying with the same `idempotency_key` returns the existing booking rather than opening
a second hold. Nothing is charged on any refusal.

## Request body

- DeveloperBookRequest
  - `search_id` string, required: `search_id` from the search response. An offer is bookable only inside its own search.
  - `offer_id` string, required: `id` of one offer from that search
  - `passengers` DeveloperPassenger[], required
    - `given_name` string, required: First/given name exactly as on the travel document
    - `family_name` string: Surname exactly as on the travel document
    - `no_family_name` boolean: True when the traveller genuinely has no surname
    - `born_on` string: YYYY-MM-DD
    - `gender` string: 'm' or 'f' — the airline form's title is derived from it
    - `email` string, email: The lead passenger's email receives the airline confirmation
    - `phone_number` string
    - `phone_country` string: ISO-3166 alpha-2, e.g. PL
    - `nationality` string: ISO-3166 alpha-2
    - `address_line1` string
    - `address_line2` string
    - `address_city` string
    - `address_state` string
    - `address_postal` string
    - `address_country` string: ISO-3166 alpha-2 country of residence
    - `passport_number` string: Optional. Asked for, never required.
    - `passport_country` string
    - `passport_expiry` string: YYYY-MM-DD
    - `passport_issue` string: YYYY-MM-DD
    - `passenger_type` 'adult' | 'child' | 'infant'
  - `contact_email` string, email: Where the confirmation goes. Defaults to the lead passenger's email.
  - `idempotency_key` string: Retrying with the same key returns the same booking instead of opening a second hold.
  - `want_seat` boolean: Pause at the airline's seat map and ask (see the booking's `question`).

## Response `202`

Hold placed and the booking agent dispatched

- BookAccepted
  - `ok` boolean
  - `booking_id` string
  - `state` string
  - `dispatched` boolean
  - `held` object: What is HELD on the connected method — not taken.
    - `amount` number
    - `currency` string
    - `card` object
  - `charged` number: Always 0 at this point. Capture happens only against a real PNR.
  - `offer` object
  - `poll_url` string, uri
  - `message` string

## Other responses

- `400`: `missing_fields` (the lead passenger is missing details an airline checkout requires — the list names them), `party_too_large`, `currency_unsupported`, `offer_not_bookable`.
- `402`: `payment_method_required`, or `payment_declined` with a `decline_reason`.
- `404`: `search_not_found` or `offer_not_found` — searches stay bookable for 6 hours.
- `409`: `package_blocked` — this traveller booked in the last 24h (package-travel rules).
- `503`: `temporarily_unavailable`.

## Changes

- **2026-09-08** `177953280ce4`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/letsfg/apis/letsfg-api/changes/api/v1/flights/book/post.md)

---

[API](https://skmtc.dev/letsfg/apis/letsfg-api.md) · [All operations](https://skmtc.dev/letsfg/apis/letsfg-api/llms.txt) · [OpenAPI document](https://skmtc.dev/letsfg/apis/letsfg-api/revisions/3c4b139ca14f?raw)
