DRCT API
Search, price, book and issue airline tickets from one REST API — no SOAP, no GDS terminal.
Sandbox vs. Production
Sandbox runs on the exact same API as production, request for request — only the data behind it is different.
- Same API, same responses. Sandbox returns real flight data for LOT, Lufthansa, Turkish Airlines and Wizz Air — production covers the major routes across every supported airline.
- Wizz Air covers the low-cost side, so you can test everything specific to low-cost carriers before going live: branded fares via Upsell, baggage and other services, seat selection and the shorter 60-minute ticketing window. Other low-cost carriers behave the same way structurally, with windows of 15–60 minutes.
- No financial risk either way: every booking and ticket in sandbox is fake, so test as much as you want — nothing costs money, and there's nothing to undo afterward.
- The same holds once you're live: every booking or ticket made through the production API also shows up in your production cabinet — drct.aero — and vice versa.
Production will accept a first or last name like "Test" or a lookalike (Testa, Testovich, Testo…) — but issuing a real ticket under one isn't allowed. That's exactly what sandbox is for.
Going live means signing a contract and getting production keys. Already talking to a sales manager? Reach out to them.
Authentication
Send your key in the Authorization header — bearer tokens only.
Your sandbox key is issued right here — — no account needed. See sandbox vs. production for how it compares to going live.
Bearer token. Example: Bearer sk_sandbox_9f2c4b…
Pins the response format, e.g. 2026-09-01. Omit it and your account default applies. See Versioning.
Turns on gzip compression for large responses like Search Offers. Send gzip, deflate and we compress the body.
Identifies a single request. Provide your own value, or omit it and we generate one — either way it's echoed back in the response. Hand it to support when you need help with a specific call.
Sandbox keys don't expire. Regenerating one in the same popup revokes the old key immediately. Treat it like a password and keep it out of client-side code.
Your First Request
Four requests, start to ticket number, each explained as you go.
Want to sell branded fares instead of the cheapest match? Slot Upsell Offer in between search and price — it's optional and doesn't change the rest of the flow.
Search offers
Post a route and a passenger list. You get back flights_options — flight combinations, each with one or more offers. An offer is a priced, bookable combination of fares from one channel.
POST /offers_search →Price the offer
Search prices are cached and can be stale. Pricing revalidates the offer against the channel and returns the amount you can actually sell, plus expire_at.
PATCH /offers/{id}/price →Create an order
Attach passenger names, contacts and documents to the offer. You receive a PNR locator and a cancel_before deadline. Nothing is paid yet.
POST /orders →Issue tickets
Payment happens here. The order moves to issued and each passenger gets a ticket number. If the price moved, you get price_changed — retry with reshop: true.
POST /orders/{id}/issue →Compression
Some responses, like offers search, can be huge so we added compression to handle it well. To enable compression, send an Accept-Encoding: gzip header.
Response Times
Response times depend on the channel and the method. Single-order calls — Price Offer, Create Order, Issue Order — are usually quick; Search Offers queries every enabled channel for the route, so it takes longer.
A slower response from a channel under load is normal, not a failure — don't retry a call just because it took longer than you expected.
1.7 seconds
the API's average response time, across every channel and method.
Idempotent Requests
Retry any POST safely — attach the same Idempotency-Key and you get back the original response instead of a duplicate.
Only Create Order and Issue Order support idempotency keys today.
The key is client-generated — we recommend a V4 UUID, up to 255 characters. We store the first response, success or error, and replay it for any repeat within at least 4 hours; after that, the same key starts a fresh request.
Reusing a live key with different parameters returns an error instead of running the request twice. Retrying is always safe when the endpoint never started processing — an invalid request or a conflicting concurrent call stores nothing.
Versioning
Your account defaults to the latest version (2026-09-01) — no need to send one. To pin an older version, send a DRCT-Version header.
We release a new version only when backward-incompatible changes are made. These are considered backward-compatible:
- Adding new API resources.
- Adding new optional request parameters to existing API methods.
- Adding new attributes to existing API responses.
- Changing the order of properties in existing API responses.
- Changing the length or format of IDs.
Track every release in the changelog.
| Version | Status |
|---|---|
| 2026-09-01 | Current |
| 2021-06-01 | Supported |
Offer ids are channel-prefixed strings. Order ids are UUIDs. Treat both as opaque.
Error codes
Every failure returns the same envelope: an HTTP status plus error.code to branch on and error.message to log. Branch on the code, never on the message text.
| Status | Code & meaning |
|---|---|
| 401 | unauthorized Key missing, malformed, or used against the wrong environment. |
| 404 | not_ The order does not exist, or was deleted after cancel_before. |
| 422 | validation_ Body failed validation. The message names the offending path, e.g. passengers.0.document. |
| 422 | offer_ The offer expired or belongs to another search. Search and price again. |
| 422 | price_ Message carries old, new and currency. Show the new price, then re-issue with reshop: true. |
| 422 | channel_ Returned by Upsell for channels that don't expose offer details. |
| 422 | refund_ Returned by Quote Order Refund/Refund Order when the channel doesn't support refunds, or the order can still be voided instead. |
| 422 | refund_ Returned by Refund Order when the order's refund state changed since it was last quoted — quote it again first. |
| 422 | channel_ Returned by View Seat Maps/Add Seats when the channel doesn't support seat maps, or ticketing is off for your agency — and by Shop Ancillaries/Confirm Ancillary when it doesn't support ancillaries at all. |
| 422 | invalid_ Returned by Add Seats when the order's status doesn't allow seat selection anymore (e.g. already issued) — and by Shop Ancillaries/Confirm Ancillary when the channel only supports ancillaries in a different order state (e.g. only after issue). |
| 422 | ancillary_ Returned by Confirm Ancillary when quantity is above 1 on a channel that sells baggage as fixed lots (WizzAir) — the lot size is already part of the ancillary id, so omit quantity or set it to 1. |
| 422 | issue_ Returned by Issue Order when the fare requires a seat for every passenger and at least one hasn't been assigned yet — add seats with Add Seats first. |
| 422 | provider_ Passed through as-is from the airline. Double-check your request against the offer, or contact support if it keeps happening. |
| 501 | not_ The channel does not support this operation — degrade gracefully rather than retrying. |
A 5xx means the failure is on our side. It's safe to retry with exponential backoff — nothing was applied.
Troubleshooting
The five failures partners hit most often, and what to do about each.
Search returns an empty array
Check the date is in the future in local time, and that your account has a channel enabled for that route. A carrier filter with no matching inventory also returns empty.
Offer expired between price and book
Offers live 30 minutes at most. Re-run search, match the itinerary by segment ids, then price the new offer. Never cache offer ids across sessions.
Issue timed out
The ticket may still have been issued. Call Get Order and check status before retrying — a blind retry can double-issue.
Name or document rejected at issue
Latin characters only, no diacritics; names must match the document exactly. Some carriers require title and date_of_birth.
Search is slow
Send Accept-Encoding: gzip, narrow with filters.carriers, and avoid firing multi-city searches in parallel on one key.
Send us the X-Trace-Id from the response headers plus the timestamp — that’s enough to trace a call end to end.
Contact supportSearch Offers
Returns bookable flight combinations for the given routes and passengers in flights — one-way, round-trip and multi-city alike. Price the one you pick before creating an order.
Segments, fares and passengers are de-duplicated: returned once at the top level, referenced by id from each option. No matches → 200 with an empty array, not an error.
See the Flights Search guide.
Sandbox only covers LOT (LO), Lufthansa (LH), Turkish Airlines (TK) and Wizz Air (W6) — other carriers return an empty flights array. See sandbox vs. production.
Origin–destination pairs, at least one. Order matters: item two is the return leg of a round trip.
One item per traveller, each with type: ADT adult, CHD child, INF infant. Max 9. No names at this stage.
A set of filters to reduce the response.
Response fields
One entry per bookable flight-and-price combination. Each option references flights, fares and passengers by id from the top-level arrays below rather than repeating them.
The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).
The fare rules and cabin/baggage conditions for this booking.
Every flown leg referenced by the options above — airports, carrier, and scheduled times, de-duplicated at the top level.
Price Offer
Revalidates an offer and returns the current price, full fare details and a fresh expire_at. Required before creating an order: search results are cached and can be stale.
Offer id from Search Offers, e.g. LH_6322c0cc1af833380f7e7bc5.
Response fields
Identifies this offer. Use it in Price Offer and Create Order.
Name of the channel this offer came from, matching a channel on your DRCT account.
When this offer stops being bookable. Depends on the channel; max TTL is 30 minutes.
Total price for all passengers on this offer.
Price broken down by passenger type — how the total above is composed.
The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).
The journeys this offer covers, each with its own fully-detailed segments.
The fare rules and cabin/baggage conditions for this booking.
Related: Upsell Offer lists branded fares for the same itinerary; Fare Rules returns the carrier’s rule text.
Upsell Offer
Returns every branded fare (fare family) for an offer's itinerary — same flights, different baggage and change rules. Same response shape as Search Offers.
See the Offer Upsell guide.
Search Offers already returns every fare on every other channel — Upsell only exists for channels where it doesn't. This list grows as more such channels are added.
- Turkish
- British Airways
- Emirates
- Finnair
- Travelport
- FlyNas
- WizzAir
- Flayer K (Ryanair)
Response fields
One entry per bookable flight-and-price combination. Each option references flights, fares and passengers by id from the top-level arrays below rather than repeating them.
The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).
The fare rules and cabin/baggage conditions for this booking.
Every flown leg referenced by the options above — airports, carrier, and scheduled times, de-duplicated at the top level.
Fare Rules
Returns the provider's raw rule text, grouped by route and category, for showing change and refund conditions verbatim. Channels without it return 501 not_implemented_error — use fares.changes and fares.cancellation instead. For a booked order, use Order Fare Rules.
Response fields
The departure–arrival airport pair this rule applies to.
Short, provider-defined name of the rule.
Provider-defined category code for the rule.
The rule's full text, verbatim from the provider — display as-is, don't parse it.
Shop Offer Ancillaries
Shop baggage for an priced offer before the order exists, then pass the chosen ids in Create Order's ancillaries array. Channels without offer baggage return channel_is_off.
Response fields
The ancillary services available for the order.
Create Order
Creates a booking for one offer and up to nine passengers, and returns a PNR. Names must match the travel document exactly. The order is held until cancel_before, then deleted automatically.
At least one phone number and one email address are required across the passenger list — not per passenger.
See the Order Creation guide.
The priced offer you are booking. Expired offers return offer_not_found.
Reuse the id and type returned by search or pricing.
Baggage shopped on the offer via Shop Offer Ancillaries and confirmed together with the order. Supported only on channels with offer baggage enabled (e.g. FlyOne); other channels return channel_is_off.
Response fields
Identifies this order.
Name of the channel this order was booked through.
The PNR locator supplied by the provider — what you'd read out to the passenger.
Current lifecycle status of the order — changes as operations are applied to it. pending means the provider accepted the order for issuing but hasn't returned tickets yet; it becomes issued once they arrive. Only returned on DRCT-Version: 2026-09-01 — on 2021-06-01 the same order still reports as issued. Turns refunded once a confirmed refund settles — see the Refund guide.
PNR locators supplied directly by the airlines involved, when different from the top-level locator.
If the order isn't issued by this time, it's deleted automatically.
When the guaranteed price expires — present only while the order is new. Past this time, re-quote via Reprice Order before issuing with reshop: true. Continuous-pricing channels never freeze a price, so this is set to the booking time (already in the past) as a standing signal to always re-quote first.
The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).
The journeys booked on this order, each with its own fully-detailed segments.
The fare rules and cabin/baggage conditions for this booking.
Contact phone numbers collected for this order.
Contact email addresses collected for this order.
Electronic tickets issued for this order, one per passenger per fare.
Identity documents collected for the passengers on this order.
Ancillary services confirmed on this order.
Seats confirmed on this order, see the Seat Maps guide.
Electronic documents (EMDs) issued on this order — e.g. a confirmed seat or a post-booking ancillary, once ticketed.
Total price for the order.
Price broken down by passenger type.
When the order was deleted, if it has been.
Creating an order does not hold the price. Between create and issue the channel may reprice — handle price_changed.
Issue Order
Pays for the order and issues electronic tickets. Status becomes issued and every passenger gets a ticket number. This is the only irreversible call in the flow.
See the Tickets Issuing guide.
Send true to accept a new price after a price_changed error. With false the call fails instead of charging a different amount.
type: cash (default, settled against your DRCT balance) or credit_card with holder, number, expiry and CVV. Card payment is enabled per channel.
Response fields
Identifies this order.
Name of the channel this order was booked through.
The PNR locator supplied by the provider — what you'd read out to the passenger.
Current lifecycle status of the order — changes as operations are applied to it. pending means the provider accepted the order for issuing but hasn't returned tickets yet; it becomes issued once they arrive. Only returned on DRCT-Version: 2026-09-01 — on 2021-06-01 the same order still reports as issued. Turns refunded once a confirmed refund settles — see the Refund guide.
PNR locators supplied directly by the airlines involved, when different from the top-level locator.
If the order isn't issued by this time, it's deleted automatically.
When the guaranteed price expires — present only while the order is new. Past this time, re-quote via Reprice Order before issuing with reshop: true. Continuous-pricing channels never freeze a price, so this is set to the booking time (already in the past) as a standing signal to always re-quote first.
The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).
The journeys booked on this order, each with its own fully-detailed segments.
The fare rules and cabin/baggage conditions for this booking.
Contact phone numbers collected for this order.
Contact email addresses collected for this order.
Electronic tickets issued for this order, one per passenger per fare.
Identity documents collected for the passengers on this order.
Ancillary services confirmed on this order.
Seats confirmed on this order, see the Seat Maps guide.
Electronic documents (EMDs) issued on this order — e.g. a confirmed seat or a post-booking ancillary, once ticketed.
Total price for the order.
Price broken down by passenger type.
When the order was deleted, if it has been.
Issuing can take up to 60 seconds on some channels. Use a long client timeout and treat a timeout as unknown, not failed: re-read the order with Get Order before retrying.
Some fares require a seat for every passenger before you can issue. If one hasn't been selected yet, this returns issue_required_seats — add it with Add Seats first.
Get Order
Returns an order's current state: status, PNR locators, passengers, flights, fares, ancillaries and tickets. Treat it as the source of truth after any failed or timed-out call. GET /orders lists all your orders.
Response fields
Identifies this order.
Name of the channel this order was booked through.
The PNR locator supplied by the provider — what you'd read out to the passenger.
Current lifecycle status of the order — changes as operations are applied to it. pending means the provider accepted the order for issuing but hasn't returned tickets yet; it becomes issued once they arrive. Only returned on DRCT-Version: 2026-09-01 — on 2021-06-01 the same order still reports as issued. Turns refunded once a confirmed refund settles — see the Refund guide.
PNR locators supplied directly by the airlines involved, when different from the top-level locator.
If the order isn't issued by this time, it's deleted automatically.
When the guaranteed price expires — present only while the order is new. Past this time, re-quote via Reprice Order before issuing with reshop: true. Continuous-pricing channels never freeze a price, so this is set to the booking time (already in the past) as a standing signal to always re-quote first.
The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).
The journeys booked on this order, each with its own fully-detailed segments.
The fare rules and cabin/baggage conditions for this booking.
Contact phone numbers collected for this order.
Contact email addresses collected for this order.
Electronic tickets issued for this order, one per passenger per fare.
Identity documents collected for the passengers on this order.
Ancillary services confirmed on this order.
Seats confirmed on this order, see the Seat Maps guide.
Electronic documents (EMDs) issued on this order — e.g. a confirmed seat or a post-booking ancillary, once ticketed.
Total price for the order.
Price broken down by passenger type.
When the order was deleted, if it has been.
List Orders
Returns all your orders as a JSON array, each shaped like a Get Order response. No filtering or pagination — to check one booking, call Get Order.
Response fields
The response is an array; every item is an order with the fields below.
Identifies this order.
Name of the channel this order was booked through.
The PNR locator supplied by the provider — what you'd read out to the passenger.
Current lifecycle status of the order — changes as operations are applied to it. pending means the provider accepted the order for issuing but hasn't returned tickets yet; it becomes issued once they arrive. Only returned on DRCT-Version: 2026-09-01 — on 2021-06-01 the same order still reports as issued. Turns refunded once a confirmed refund settles — see the Refund guide.
PNR locators supplied directly by the airlines involved, when different from the top-level locator.
If the order isn't issued by this time, it's deleted automatically.
When the guaranteed price expires — present only while the order is new. Past this time, re-quote via Reprice Order before issuing with reshop: true. Continuous-pricing channels never freeze a price, so this is set to the booking time (already in the past) as a standing signal to always re-quote first.
The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).
The journeys booked on this order, each with its own fully-detailed segments.
The fare rules and cabin/baggage conditions for this booking.
Contact phone numbers collected for this order.
Contact email addresses collected for this order.
Electronic tickets issued for this order, one per passenger per fare.
Identity documents collected for the passengers on this order.
Ancillary services confirmed on this order.
Seats confirmed on this order, see the Seat Maps guide.
Electronic documents (EMDs) issued on this order — e.g. a confirmed seat or a post-booking ancillary, once ticketed.
Total price for the order.
Price broken down by passenger type.
When the order was deleted, if it has been.
Reprice Order
Re-quotes the current price of an order and reports whether it changed. Call this once price_expires_at has passed, then issue with reshop: true to accept the new price.
See the Price Change guide.
Response fields
The order's price before this re-quote.
The freshly quoted price — pass this to issue if price_changed is true.
Whether new_price actually differs from old_price.
Order Fare Rules
The same raw rule text as Fare Rules, for an existing order. Channels without it return 501 not_implemented_error — use the order's fares.changes and fares.cancellation instead.
Response fields
The departure–arrival airport pair this rule applies to.
Short, provider-defined name of the rule.
Provider-defined category code for the rule.
The rule's full text, verbatim from the provider — display as-is, don't parse it.
Cancel Order
Cancels an order and returns it with deleted_at set. Unissued orders cancel free; for issued tickets, fees follow fares.cancellation. Past the void window, refund it instead — see the the Refund guide.
See the Order Cancellation guide.
Response fields
Identifies this order.
Name of the channel this order was booked through.
The PNR locator supplied by the provider — what you'd read out to the passenger.
Current lifecycle status of the order — changes as operations are applied to it. pending means the provider accepted the order for issuing but hasn't returned tickets yet; it becomes issued once they arrive. Only returned on DRCT-Version: 2026-09-01 — on 2021-06-01 the same order still reports as issued. Turns refunded once a confirmed refund settles — see the Refund guide.
PNR locators supplied directly by the airlines involved, when different from the top-level locator.
If the order isn't issued by this time, it's deleted automatically.
When the guaranteed price expires — present only while the order is new. Past this time, re-quote via Reprice Order before issuing with reshop: true. Continuous-pricing channels never freeze a price, so this is set to the booking time (already in the past) as a standing signal to always re-quote first.
The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).
The journeys booked on this order, each with its own fully-detailed segments.
The fare rules and cabin/baggage conditions for this booking.
Contact phone numbers collected for this order.
Contact email addresses collected for this order.
Electronic tickets issued for this order, one per passenger per fare.
Identity documents collected for the passengers on this order.
Ancillary services confirmed on this order.
Seats confirmed on this order, see the Seat Maps guide.
Electronic documents (EMDs) issued on this order — e.g. a confirmed seat or a post-booking ancillary, once ticketed.
Total price for the order.
Price broken down by passenger type.
When the order was deleted, if it has been.
Quote Order Refund
Asks the airline what a refund would return, without refunding. Nothing moves until you confirm with POST /orders/{id}/refund. Safe to repeat — every call re-quotes.
See the Refund guide.
Response fields
The PNR locator supplied by the provider.
What lands back on your balance — the penalty is already deducted from it, don't subtract it again.
What the airline keeps. Zero when the fare is fully refundable.
The fare half of total. Omitted when the provider doesn't break the refund down.
The taxes half of total. Omitted when the provider doesn't break the refund down.
Refund Order
Confirms the refund quoted by GET /orders/{id}/refund_quote: the tickets are refunded with the airline and your balance is topped up. A document not yet REFUNDED is still processing — poll GET /orders/{id} for the final state.
See the Refund guide.
Response fields
Identifies the order this refund belongs to.
The PNR locator supplied by the provider.
Name of the channel this order was booked through.
The order's status after confirming the refund — the documents below may still be processing individually.
The total returned to your balance, covering documents the airline has already processed. Omitted while every document is still pending, or when they were refunded in different currencies.
The total penalty held back, on the same terms as total.
Every ticket the refund covered.
Refunded EMDs — present only when the order had ancillaries.
Ancillaries
Ancillaries guide: which channels sell ancillaries, what each offers and when.
Lists the extras you can add to an existing order: bags and services such as priority boarding, check-in or special assistance.
Response fields
The ancillary services available for the order.
Confirm Ancillary
Adds ancillaries to the order — one item per ancillary per passenger, several per request. Returns the updated order with an ancillaries array, as in Get Order.
Some channels sell ancillaries only before issue, others only after. In the wrong order state you get invalid_order_status.
See the Ancillaries guide.
One item per ancillary being confirmed. Batch several — for different passengers or ancillaries — in a single request.
Response fields
Identifies this order.
Name of the channel this order was booked through.
The PNR locator supplied by the provider — what you'd read out to the passenger.
Current lifecycle status of the order — changes as operations are applied to it. pending means the provider accepted the order for issuing but hasn't returned tickets yet; it becomes issued once they arrive. Only returned on DRCT-Version: 2026-09-01 — on 2021-06-01 the same order still reports as issued. Turns refunded once a confirmed refund settles — see the Refund guide.
PNR locators supplied directly by the airlines involved, when different from the top-level locator.
If the order isn't issued by this time, it's deleted automatically.
When the guaranteed price expires — present only while the order is new. Past this time, re-quote via Reprice Order before issuing with reshop: true. Continuous-pricing channels never freeze a price, so this is set to the booking time (already in the past) as a standing signal to always re-quote first.
The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).
The journeys booked on this order, each with its own fully-detailed segments.
The fare rules and cabin/baggage conditions for this booking.
Contact phone numbers collected for this order.
Contact email addresses collected for this order.
Electronic tickets issued for this order, one per passenger per fare.
Identity documents collected for the passengers on this order.
Ancillary services confirmed on this order.
Seats confirmed on this order, see the Seat Maps guide.
Electronic documents (EMDs) issued on this order — e.g. a confirmed seat or a post-booking ancillary, once ticketed.
Total price for the order.
Price broken down by passenger type.
When the order was deleted, if it has been.
View Seat Maps
Returns the seat map for an existing order, one per passenger per segment. Unavailable seats are included, so you can render the whole cabin.
| Channel | When seats can be added |
|---|---|
| Flayer K (Ryanair) | On an unissued order, before issue |
| WizzAir | On an unissued order, before issue |
If the channel doesn't support seat maps, both this request and Add Seats return channel_is_off.
See the Seat Maps guide.
Response fields
One entry per passenger per segment.
Add Seats
Assigns seats to the order, one item per passenger per segment. Returns the updated order with the seats in seat_maps; once issued, each also appears in emds.
Some fares require a seat for every passenger before you can issue. If one hasn't been selected yet, Issue Order returns issue_required_seats — add seats first.
See the Seat Maps guide.
One item per seat you're assigning.
Response fields
Identifies this order.
Name of the channel this order was booked through.
The PNR locator supplied by the provider — what you'd read out to the passenger.
Current lifecycle status of the order — changes as operations are applied to it. pending means the provider accepted the order for issuing but hasn't returned tickets yet; it becomes issued once they arrive. Only returned on DRCT-Version: 2026-09-01 — on 2021-06-01 the same order still reports as issued. Turns refunded once a confirmed refund settles — see the Refund guide.
PNR locators supplied directly by the airlines involved, when different from the top-level locator.
If the order isn't issued by this time, it's deleted automatically.
When the guaranteed price expires — present only while the order is new. Past this time, re-quote via Reprice Order before issuing with reshop: true. Continuous-pricing channels never freeze a price, so this is set to the booking time (already in the past) as a standing signal to always re-quote first.
The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).
The journeys booked on this order, each with its own fully-detailed segments.
The fare rules and cabin/baggage conditions for this booking.
Contact phone numbers collected for this order.
Contact email addresses collected for this order.
Electronic tickets issued for this order, one per passenger per fare.
Identity documents collected for the passengers on this order.
Ancillary services confirmed on this order.
Seats confirmed on this order, see the Seat Maps guide.
Electronic documents (EMDs) issued on this order — e.g. a confirmed seat or a post-booking ancillary, once ticketed.
Total price for the order.
Price broken down by passenger type.
When the order was deleted, if it has been.
List Reporting Tickets
Returns every ticket from issued orders for back-office reconciliation — nett totals, commission, ticketing office and last transaction time. JSON or XML.
Defaults to the start of today. Filters by last transaction time.
Defaults to the end of today.
Ticket number. Used only when locator is blank.
Booking reference (PNR). Takes precedence over number and the date range.
Response fields
Identifies this ticket.
The electronic document number issued by the airline.
Current document status — changes as operations are applied to it. refund means a confirmed refund covered this ticket — see the Refund guide.
The PNR locator supplied by the provider — what you'd read out to the passenger.
Ticket or EMD (electronic miscellaneous document).
When the order this ticket belongs to was created.
When this ticket's status last changed.
Total price of the ticket.
The fare portion of the price, excluding taxes.
The tax portion of the price, excluding fare.
Agent commission applied to this document.
Agent commission applied to this document, converted to the account's settlement currency.
The user who issued this document.
The company this document was issued under.
Office id that issued the ticket.
Office id that created the order.
Name of the channel this order was booked through.
The passenger this document was issued for.
The flown legs covered by this document.
Flights Search
Search for flights
To make a search you need to provide details of the desired flight and information about the passengers. Here is an example:
❗ Depending on the airline, an offer lives up to 30 minutes after it was created from Search Offers.
Testing tip: sandbox only covers LOT, Lufthansa, Turkish Airlines and Wizz Air. See sandbox vs. production.
Price an offer
Search prices are cached, so always price the offer before booking to get the current price. Full reference: Price Offer reference.
Order Creation
Now you can order your flight 👨💻
To make an order you need:
- the ID of the offer you’d like to book;
- basic necessary information about the passengers.
❗ Airlines allow at most three orders per passenger on the same route; more can incur penalties.
❗ Use real names. Carriers treat fake names or random letters as test bookings and charge penalties.
💡 Some providers require more data to create an order (e.g. date of birth, title). In that case you will receive a validation_error with an appropriate message.
We strongly encourage sending a unique Idempotency-Key header in a request.
Tickets Issuing
To issue tickets you need the id of the order. If you still haven’t learned how to create an order, begin from the quickstart guide.
❗ You have a time limit, cancel_before, at which the airline may release your seats and you won’t be able to issue the order.
Payment
We support cash and credit card payments. Cash payments will be processed via your BSP according to your contracts with airlines. Cash is the default.
Card payment works on some channels only; elsewhere you get channel_not_supported. Ask support for the list.
For specification and examples, see the Issue Order reference.
Request issue 👉
Price changed
Prices can change with currency rates, typically when you create an order one day and issue it the next. Issuing at the old price returns price_changed.
To proceed with issuing, send the issue request again with a { "reshop": true } body.
Delayed tickets
Some providers do not return tickets in the issue response. In that case you still receive an issued status, but tickets appear later in the Get Order response.
It usually takes up to 3 minutes to receive tickets. If you have not received tickets within an hour, contact support.
Order Cancellation
To perform a cancellation you need the id of the order you would like to cancel. If you still haven’t learned how to create an order, look at the quickstart guide.
❗ Some airlines do not support cancellation of an order with tickets. In this case we return a not_implemented_error.
Cancellation process
Cancellation works for orders without tickets as well as with tickets. When an order has tickets, our system voids them first and then cancels the order.
Voiding only works inside the ticketing window — once an order is past it, this endpoint stops accepting it. Refund it instead: see the Refund guide.
You need to understand two attributes to perform cancellation correctly:
- cancel_before and cancellation in fares;
- cancel_before is the time before which you can safely cancel an order without tickets;
- cancellation shows the cancellation policy for orders with tickets.
Order without tickets
Generally, you can safely cancel an order anytime without additional fees. But some airlines may fine you if you do not cancel before the time in cancel_before.
💡 All timings are based on the time of the Point of Sale (POS) of the order — for a UA POS that means 23:59 GMT+3.
❗ If you cancel an order less than three hours before your flight, the airline may fine you.
Cancellation request 👉
Refund
A refund is two calls: quote it to see what you get back and what the airline keeps, then confirm it. Nothing moves until you confirm.
⚠️ An order that can still be voided must be voided, not refunded — void returns the full amount. Cancel it with DELETE /orders/{id}; while void is possible, a refund returns refund_unavailable.
Availability
Refund is available on the channels whose airline supports it — the same list the DRCT board offers, no separate API opt-in:
Aegean Airlines, Air France-KLM NDC, Ajet, British Airways, Finnair, Flydubai, FlyOne, Lot, Lufthansa Group, Pegasus, Qatar Airways, Riyadh Air, Saudia, Singapore Airlines, Turkish Airlines.
The order must also be issued and past its void window. And if a channel restricts refunds to DRCT staff, the API answers refund_unavailable there too — same rule as the board.
Quote the Refund
Field notes:
- total — what lands back on your balance. The penalty is already deducted from it; don't subtract it a second time.
- penalty — what the airline keeps. 0 when the fare is fully refundable.
- fare / taxes — the two halves of total. Omitted when the airline does not break the refund down, so treat them as optional.
💡 A quote never refunds anything — hence a GET. Call it again whenever you need the current figure: every call re-asks the airline, so don't cache the response.
Confirm the Refund
Field notes:
- status — the order status after the refund.
- tickets / emds — every document the refund covered. emds is present only when the order had ancillaries.
- total / penalty at the top level — the totals across the documents the airline has already processed.
Once every document is REFUNDED, we cancel the booking with the airline for you, as DELETE /orders/{id} would. The order is then read-only — no issue, ancillaries or second refund — and GET /orders/{id} shows status: "refunded".
Pending Documents and Totals
While a document is still pending we leave it out of the totals rather than reporting a settled zero. So a response can legitimately look like this:
No total, no penalty — nothing has settled yet. The same happens when an order's documents were refunded in different currencies: the per-document amounts are there, because summing them would mean us picking an exchange rate on your behalf.
Errors
| Code | When |
|---|---|
| refund_unavailable | You cannot refund this order. Either the channel does not support refunds (or restricts them to DRCT staff), or the order is not in a refundable state — most often it can still be voided, so cancel it with DELETE /orders/{id}, or it was never issued. The message says which. |
| not_found | The order id is malformed or unknown, or the order belongs to another agency. |
A refusal from the airline itself is passed through with the provider's own code and message, so you see what the airline said rather than a generic error.
Offer Upsell
On some channels a search returns one representative offer per itinerary. Upsell expands it into every branded fare for the same flights — from Economy Basic to Business — each with its own price and offer id to price and book.
Availability
Only these channels need Upsell — everywhere else, Search Offers already returns every fare:
- Turkish
- British Airways
- Emirates
- Finnair
- Travelport
- FlyNas
- WizzAir
- Flayer K (Ryanair)
Flow overview
- Search offers (Search Offers).
- Take an offer id from the search response (flights_options[].offers[].id).
- Retrieve the upsell fares (GET /offers/:id/upsell).
- Pick a fare, then continue with Price Offer / Create Order using the offer id from the upsell response — not the original search offer.
Get Offer Upsell
Returns all bookable fares available for the specified offer. Full reference: Upsell Offer.
The response is identical to a Search Offers response — the same flights_options / fares / segments / passengers, listing every bookable branded fare for the itinerary.
💡 Use the offer id from this response (flights_options[].offers[].id) for Price Offer / Create Order — it replaces the offer id from the original search.
Ancillaries
To add ancillaries you need the id of the order you would like to extend. If you still haven’t learned how to create an order, look at the quickstart guide.
Availability
Ancillaries are not available on every channel or in every order state. Availability is driven by the order’s channel and its status.
| Channel | When ancillaries can be added | EMD issuance |
|---|---|---|
| Flayer K (Ryanair) | Before the order is issued | Confirmed together with the issued order |
| WizzAir | Before the order is issued — baggage and other services (priority boarding, check-in, special assistance) | Confirmed together with the issued order |
| Lufthansa Group | After the order is issued | Asynchronous — see Pending Ancillaries |
| Turkish | After the order is issued | Synchronous — issued immediately |
| FlyOne | Before the order exists — shopped on the offer via Shop Offer Ancillaries | Confirmed together with the order |
If the channel supports ancillaries only for a different order state (e.g. only after issue), you will get invalid_order_status. If the channel does not support ancillaries at all, you will get channel_is_off.
Flow overview
- Create an order (Create Order).
- Issue the order (Issue Order) — if required.
- Retrieve available ancillaries (GET /orders/:id/ancillaries).
- Add selected ancillaries to the order (POST /orders/:id/ancillaries).
Shop Ancillaries
Returns a list of ancillaries available for the specified order. Full reference: Shop Ancillaries.
Field notes:
- id — unique ancillary id; use it when confirming.
- type — baggage for bags, service for any other extra. Branch on this rather than on the provider's free-text name.
- segments — segment ids the ancillary is bound to (match Get Order). Empty in the shop list for a service that applies to the passenger on the whole order (e.g. special assistance); once confirmed, the order shows it with the flight's segments.
- max_quantity — maximum quantity per passenger, when the provider reports it.
- passengers — passenger ids the ancillary applies to, when the provider scopes it per passenger. Otherwise omitted, and you choose the passenger at confirm.
Confirm Ancillary
Adds selected ancillaries to the order. Full reference: Confirm Ancillary.
Request body:
- ancillaries[].id — ancillary id obtained from the Shop Ancillaries response.
- ancillaries[].passenger_id — passenger id from the order.
- ancillaries[].quantity — quantity for this passenger. Optional, defaults to 1 and must not exceed max_quantity. Channels that sell baggage as fixed lots (WizzAir) reject a value above 1 with ancillary_quantity_not_supported. Several ancillaries can be added in a single request.
After successfully adding ancillaries, the API returns a full order (see Get Order). The response includes an ancillaries section.
💡 In the shop response the passenger binding is a list (passengers), while on a confirmed ancillary it is the single assigned passenger. A confirmed ancillary also carries a status — see Pending Ancillaries.
Pending Ancillaries
Some channels issue the ancillary's EMD synchronously: it's issued as soon as you confirm. Others confirm with the provider first — the ancillary comes back pending, and its EMD is issued once the provider confirms.
The state is exposed via the status field on a confirmed ancillary in the order:
| Status | Meaning |
|---|---|
| confirmed | Added to the order and confirmed by the provider; EMD not issued yet. |
| pending | Awaiting provider confirmation; EMD issuance in progress. |
| issued | The ancillary EMD has been issued. |
| cancelled | The ancillary was cancelled / could not be issued. |
How it works
- You confirm the ancillary (POST /orders/:id/ancillaries). On an asynchronous channel (Lufthansa Group) the returned ancillary has "status": "pending".
- Our system polls the provider and issues the EMD automatically once the ancillary is confirmed — no extra call is required from you.
- Poll the order until the ancillary status becomes issued (or cancelled if issuance failed).
❗ On asynchronous channels the ancillary is not fully issued at confirm time. Do not treat pending as final — re-fetch the order to get the final status.
💡 An ancillary normally resolves from pending to issued within a few minutes. If it is still pending after ~15 minutes, contact support with the order id — do not retry the confirm request.
Seat Maps
Seats belong to an existing order, so you need its id: add them after the order is created and before it is issued. There's no offer-stage seat endpoint.
Availability
Seat maps are not available on every channel. Availability is driven by the order's channel.
| Channel | When seats can be added |
|---|---|
| Flayer K (Ryanair) | On an unissued order, before issue |
| WizzAir | On an unissued order, before issue |
If the channel does not support seat maps, both View Seat Maps and Add Seats return channel_is_off.
Some fares require a seat to be issued. Bundled fares (e.g. Ryanair Regular/Plus, WIZZ PLUS) reject issuing with issue_required_seats until a seat is selected for every passenger. Add seats before issuing the order.
View Seat Maps
GET /orders/{id}/seat_maps returns the cabin layout — one map per passenger per segment (a seat's price and availability can differ per passenger), each with the cabin and its rows.
Field notes:
- segment / passenger — the segment and passenger this map is built for; they match the ids in the order. Because price and availability can differ per passenger, there is one map per passenger per segment.
- number — the physical row number.
- designator — the seat designator (e.g. 12A); use it when adding the seat.
- available — whether the seat can be booked. Unavailable seats are included so you can render the full cabin; they just cannot be selected.
- characteristics — IATA seat characteristics as { code, description } (see the table below). Empty when the provider reports none.
- price — the final price to select the seat, as { amount, currency }. Any agency markup is already included; no breakdown is exposed.
Seat characteristics
Every code comes from the IATA seat-characteristic catalog, so the vocabulary is the same for every airline. The table lists the codes seen in current responses across the channels we've checked:
| Code | Description |
|---|---|
| 1 | Restricted seat - General |
| 8 | No seat at this location |
| 9 | Center seat (not window, not aisle) |
| A | Aisle seat |
| B | Seat with bassinet facility |
| CH | Chargeable seat |
| E | Exit and emergency exit |
| FC | Front of cabin class/compartment |
| H | Seat with facilities for handicapped/incapacitated passenger |
| IE | Seat not suitable for child |
| L | Leg space seat |
| LS | Left side of aircraft |
| M | Seat without a movie view |
| MS | Middle seat |
| N | No smoking seat |
| O | Preferential seat |
| OW | Over wing seat(s) |
| RS | Right side of aircraft |
| U | Seat suitable for unaccompanied minors |
| V | Seat to be left vacant or offered last |
| W | Window seat |
| 1A | Seat not allowed for infant |
| 1B | Seat not allowed for medical |
| 1C | Seat not allowed for unaccompanied minor |
| 1D | Restricted recline seat |
| 72 | Undesirable seat |
Codes that mark a passenger restriction (1, 1A, IE, 1C) describe who may not take the seat; the seat is still returned, but it comes back available: false for the passenger it does not suit.
Add Seats
POST /orders/{id}/seat_maps adds the chosen seats. Each entry assigns one seat to one passenger on one segment.
- passenger_id — passenger id from the order.
- segment_id — segment id from the order / seat map.
- designator — seat designator from the View Seat Maps response (row number + seat letter, e.g. 12A).
The response is the updated order. Confirmed seats appear in the order's seat_maps section (and, once the order is issued, each also surfaces as an electronic document, EMD):
- passenger / segment — the passenger and segment the seat is assigned to.
- designator — the chosen seat.
- price — the final price paid for the seat, as { amount, currency }.
- status — the seat's status on the order, when the channel reports it.
Errors
| Code | When |
|---|---|
| channel_is_off | The channel does not support seat maps, or ticketing is off for your agency. |
| invalid_order_status | The order status does not allow seats (e.g. already issued). |
| validation_error | The seat_maps payload is invalid (empty list, or a bad designator). |
| not_found | The order id is unknown or the order was cancelled. |
Price Change
On most channels a quoted price holds until the order's cancel_before — though currency rates can move it overnight. A few channels guarantee the fare only until price_expires_at and re-price continuously.
Either way, watch price_expires_at on the order. If the price moves, issuing returns price_changed; issue again with { "reshop": true } to accept the new price.
Price Expires At
Continuous pricing is driven by the offer's channel and fare — not every offer on these channels carries it. Always trust the price_expires_at field on the order rather than assuming by channel.
Channels where the price can carry an expiry:
- Lufthansa Group
- Aegean Airlines
- Qatar Airways
- Saudia
- British Airways
- American Airlines
- Emirates
- United Airlines
- Finnair
- Singapore Airlines
If price_expires_at is absent, the order isn't continuously priced — the normal cancel_before time limit applies and there is nothing extra to do.
Aegean Airlines never freezes a price. For this channel price_expires_at comes back in the past, which is the signal to re-quote before every issue. Treat "expired" as the default here, not the exception.
Read the Expiry
price_expires_at is returned on the order (present only while the order is new).
Field notes:
- price_expires_at — after this moment the quoted price is no longer guaranteed.
- cancel_before — unrelated: it's when the order is auto-cancelled. The price can expire well before that.
Re-quote the Price
Ask for a fresh quote and compare it to the current order price.
Field notes:
- price_changed — true when the re-quoted price differs from the current order price. When false, nothing moved and you can issue normally.
- old_price / new_price — omitted when the airline reports no change.
Price Changed
Prices can change with currency rates, typically when you create an order one day and issue it the next. Issuing at the stale price returns price_changed.
To proceed, send the issue request again with a { "reshop": true } body to accept the new price.
Issue Order returns the full order with tickets, priced at the amount you accepted.
Errors
| Code | When |
|---|---|
| price_changed | You tried to issue at a stale price without accepting the change. Re-issue with { "reshop": true }. |
| not_found | The order id doesn't exist in your account. |
Working with Charters
We have a channel to sell charters, with a slightly different flow.
Issuing
After issuing, wait for the provider to confirm. Once confirmed, tickets appear in Get Order; if rejected, Get Order returns a cancelled_order error with HTTP 404.
Confirmation time
Issued during the provider's support hours (6 a.m. – 3 p.m. UTC), an order is confirmed or rejected in about 15 minutes on average; otherwise, on the next working day. If the provider is silent for an hour, our support contacts them — there's no guaranteed response time.
Carriers filter
With a carriers filter in Search Offers, include our code CSH to get charters — the default filter leaves them out.
Documents
Documents are required for all passengers at the Create Order step.