API

DRCT API

Search, price, book and issue airline tickets from one REST API — no SOAP, no GDS terminal.

# sandbox — same API as production, zero cost
https://sandbox-api.drct.aero

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.

Ready for production?

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.

AuthorizationstringREQUIRED

Bearer token. Example: Bearer sk_sandbox_9f2c4b…

DRCT-VersionstringOPTIONAL

Pins the response format, e.g. 2026-09-01. Omit it and your account default applies. See Versioning.

Allowed values:2021-06-012026-09-01
Accept-EncodingstringRECOMMENDED

Turns on gzip compression for large responses like Search Offers. Send gzip, deflate and we compress the body.

X-Trace-IdstringOPTIONAL

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.

Authenticated request
curl -X POST "https://sandbox-api.drct.aero/ping" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "DRCT-Version: 2026-09-01" \
  -d '{ "payload": "Keep it logically awesome" }'
{
  "authenticated": true,
  "environment": "sandbox"
}
{
  "error": {
    "code": "unauthorized",
    "message": "API key is missing or invalid."
  }
}

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.

01

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 →
02

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 →
03

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 →
04

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 →
End-to-end · cURL
# 1 — search
curl -X POST $API/offers_search \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -d '{"flights":[{"departure_airport_code":"WAW",
        "arrival_airport_code":"FRA",
        "departure_date":"2026-09-27"}],
       "passengers":[{"type":"ADT"}]}'

# 2 — price the offer you picked
curl -X PATCH $API/offers/$OFFER_ID/price \
  -H "Authorization: Bearer $DRCT_API_KEY"

# 3 — book
curl -X POST $API/orders \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -d @passengers.json

# 4 — issue
curl -X POST $API/orders/$ORDER_ID/issue \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -d '{"reshop":false}'

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.

Compressed request
curl -X POST https://sandbox-api.drct.aero/offers_search \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Accept-Encoding: gzip" \
  -d '{ ... }'

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.

Average

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.

Request · idempotent create order
curl -X POST https://sandbox-api.drct.aero/orders \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5b1f9e2a-6e3a-4f2b-9d3e-2a6c7c1e4f0a" \
  -d '{ "offer_id": "LH_6322c0cc1af833380f7e7bc5", "passengers": [ /* … */ ] }'

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.

VersionStatus
2026-09-01Current
2021-06-01Supported
Content type

application/json

Reporting also serves application/xml.

Identifiers

Offer ids are channel-prefixed strings. Order ids are UUIDs. Treat both as opaque.

Request · ping
curl -X POST "https://sandbox-api.drct.aero/ping" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "DRCT-Version: 2026-09-01" \
  -d '{ "payload": "Keep it logically awesome" }'

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.

StatusCode & meaning
401
unauthorized

Key missing, malformed, or used against the wrong environment.

404
not_found

The order does not exist, or was deleted after cancel_before.

422
validation_error

Body failed validation. The message names the offending path, e.g. passengers.0.document.

422
offer_not_found

The offer expired or belongs to another search. Search and price again.

422
price_changed

Message carries old, new and currency. Show the new price, then re-issue with reshop: true.

422
channel_not_supported

Returned by Upsell for channels that don't expose offer details.

422
refund_unavailable

Returned by Quote Order Refund/Refund Order when the channel doesn't support refunds, or the order can still be voided instead.

422
refund_not_allowed

Returned by Refund Order when the order's refund state changed since it was last quoted — quote it again first.

422
channel_is_off

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_order_status

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_quantity_not_supported

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_required_seats

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_error

Passed through as-is from the airline. Double-check your request against the offer, or contact support if it keeps happening.

501
not_implemented_error

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.

Error envelope
{
  "error": {
    "code": "price_changed",
    "message": "{'old':186.9,'new':191.4,'currency':'EUR'}"
  }
}

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.

Still stuck

Send us the X-Trace-Id from the response headers plus the timestamp — that’s enough to trace a call end to end.

Contact support

Search Offers

POSThttps://sandbox-api.drct.aero/offers_search

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.

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.

Body parameters
flightsarray of objectsREQUIRED

Origin–destination pairs, at least one. Order matters: item two is the return leg of a round trip.

>= 1 items
departure_airport_codestring

IATA 3-letter code.

Example:WAWMatch pattern:^[A-Z]{3}$
arrival_airport_codestring

IATA 3-letter code.

Example:FRAMatch pattern:^[A-Z]{3}$
departure_datedate

YYYY-MM-DD in the departure airport’s local time.

Example:2026-09-27
passengersarray of objectsREQUIRED

One item per traveller, each with type: ADT adult, CHD child, INF infant. Max 9. No names at this stage.

>= 1 items
typestringREQUIRED

Name of a group of passengers.

Allowed values:ADTCHDINF
filtersobject

A set of filters to reduce the response.

carriersarray of strings

IATA 2-letter carrier codes. Only options whose validating carrier is in the list are returned — the cheapest way to shrink a slow search.

Example:["LH"]Match pattern:^[\dA-Z]{2}$

Response fields

flights_optionsarray of objectsREQUIRED

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.

passengersarray of objectsREQUIRED

The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).

faresarray of objectsREQUIRED

The fare rules and cabin/baggage conditions for this booking.

segmentsarray of objectsREQUIRED

Every flown leg referenced by the options above — airports, carrier, and scheduled times, de-duplicated at the top level.

An empty array — no offers matched these parameters, nothing to describe.

errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:validation_error
messagestringREQUIRED

A human-readable error description.

Example:{'flights':['size cannot be less than 1']}
Request
curl -X POST https://sandbox-api.drct.aero/offers_search \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "DRCT-Version: 2026-09-01" \
  -H "Content-Type: application/json" \
  -d '{
    "flights": [
      {
        "departure_airport_code": "WAW",
        "arrival_airport_code": "FRA",
        "departure_date": "2026-09-27"
      }
    ],
    "passengers": [{ "type": "ADT" }]
  }'
curl -X POST https://sandbox-api.drct.aero/offers_search \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "DRCT-Version: 2026-09-01" \
  -H "Content-Type: application/json" \
  -d '{
    "flights": [
      {
        "departure_airport_code": "WAW",
        "arrival_airport_code": "FRA",
        "departure_date": "2026-09-27"
      },
      {
        "departure_airport_code": "FRA",
        "arrival_airport_code": "WAW",
        "departure_date": "2026-09-28"
      }
    ],
    "passengers": [{ "type": "ADT" }]
  }'
curl -X POST https://sandbox-api.drct.aero/offers_search \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "DRCT-Version: 2026-09-01" \
  -H "Content-Type: application/json" \
  -d '{
    "flights": [
      {
        "departure_airport_code": "MUC",
        "arrival_airport_code": "FRA",
        "departure_date": "2026-09-27"
      },
      {
        "departure_airport_code": "IST",
        "arrival_airport_code": "WAW",
        "departure_date": "2026-09-28"
      }
    ],
    "passengers": [{ "type": "ADT" }]
  }'
curl -X POST https://sandbox-api.drct.aero/offers_search \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "DRCT-Version: 2026-09-01" \
  -H "Content-Type: application/json" \
  -d '{
    "flights": [
      {
        "departure_airport_code": "WAW",
        "arrival_airport_code": "FRA",
        "departure_date": "2026-09-27"
      }
    ],
    "passengers": [{ "type": "ADT" }],
    "filters": { "carriers": ["LH"] }
  }'
{
  "flights_options": [
    {
      "flights": [
        {
          "id": "F1",
          "journey_time": 5400,
          "segments": ["63299a951af83376ede02bb8"]
        }
      ],
      "offers": [
        {
          "id": "LH_6322c0cc1af833380f7e7bc5",
          "channel": "Lufthansa Group",
          "expire_at": "2026-09-27T14:46:15+03:00",
          "price": { "amount": 182.4, "currency": "EUR" }
        }
      ]
    }
  ],
  "segments": [ /* … */ ],
  "fares": [ /* … */ ],
  "passengers": [{ "id": "PAX1", "type": "ADT" }]
}
// nothing available for these parameters
[]
{
  "error": {
    "code": "validation_error",
    "message": "{'flights':['size cannot be less than 1']}"
  }
}

Price Offer

PATCHhttps://sandbox-api.drct.aero/offers/{id}/price

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.

idpath · stringREQUIRED

Offer id from Search Offers, e.g. LH_6322c0cc1af833380f7e7bc5.

Response fields

idstringREQUIRED

Identifies this offer. Use it in Price Offer and Create Order.

Example:LH_6322c0cc1af833380f7e7bc5
channelstringREQUIRED

Name of the channel this offer came from, matching a channel on your DRCT account.

Example:Lufthansa Group
expire_atdate-time

When this offer stops being bookable. Depends on the channel; max TTL is 30 minutes.

Example:2022-09-20T14:46:15+03:00
priceobjectREQUIRED

Total price for all passengers on this offer.

price_detailsarray of objectsREQUIRED

Price broken down by passenger type — how the total above is composed.

passengersarray of objectsREQUIRED

The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).

>= 1 items
flightsarray of objectsREQUIRED

The journeys this offer covers, each with its own fully-detailed segments.

>= 1 items
faresarray of objectsREQUIRED

The fare rules and cabin/baggage conditions for this booking.

>= 1 items
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:validation_error
messagestringREQUIRED

A human-readable error description.

Example:'value' is not a valid offer_id

Related: Upsell Offer lists branded fares for the same itinerary; Fare Rules returns the carrier’s rule text.

Request
curl -X PATCH \
  https://sandbox-api.drct.aero/offers/LH_6322c0cc/price \
  -H "Authorization: Bearer $DRCT_API_KEY"
{
  "id": "LH_6322c0cc1af833380f7e7bc5",
  "channel": "Lufthansa Group",
  "expire_at": "2026-09-27T14:46:15+03:00",
  "price": { "amount": 186.9, "currency": "EUR" },
  "price_details": [
    {
      "type": "ADT",
      "count": 1,
      "price": { "amount": 186.9, "currency": "EUR" }
    }
  ],
  "fares": [ /* cabin_class, baggage, changes… */ ]
}
{
  "error": {
    "code": "validation_error",
    "message": "'value' is not a valid offer_id"
  }
}

Upsell Offer

GEThttps://sandbox-api.drct.aero/offers/{id}/upsell

Returns every branded fare (fare family) for an offer's itinerary — same flights, different baggage and change rules. Same response shape as Search Offers.

Available on

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

flights_optionsarray of objectsREQUIRED

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.

passengersarray of objectsREQUIRED

The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).

faresarray of objectsREQUIRED

The fare rules and cabin/baggage conditions for this booking.

segmentsarray of objectsREQUIRED

Every flown leg referenced by the options above — airports, carrier, and scheduled times, de-duplicated at the top level.

errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:validation_error
messagestringREQUIRED

A human-readable error description.

Example:'value' is not a valid offer_id
{
  "flights_options": [
    {
      "flights": [
        {
          "id": "F1",
          "journey_time": 5400,
          "segments": ["63299a951af83376ede02bb8"]
        }
      ],
      "offers": [
        {
          "id": "LH_6322c0cc1af833380f7e7bc5",
          "channel": "Lufthansa Group",
          "expire_at": "2026-09-27T14:46:15+03:00",
          "price": { "amount": 182.4, "currency": "EUR" }
        },
        {
          "id": "LH_6322c0cc1af833380f7e7bc6",
          "channel": "Lufthansa Group",
          "expire_at": "2026-09-27T14:46:15+03:00",
          "price": { "amount": 238.9, "currency": "EUR" }
        }
      ]
    }
  ],
  "segments": [ /* … */ ],
  "fares": [ /* … */ ],
  "passengers": [{ "id": "PAX1", "type": "ADT" }]
}
{
  "error": {
    "code": "validation_error",
    "message": "'value' is not a valid offer_id"
  }
}

Fare Rules

GEThttps://sandbox-api.drct.aero/offers/{id}/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

routestringREQUIRED

The departure–arrival airport pair this rule applies to.

Example:FRA-CDG
namestringREQUIRED

Short, provider-defined name of the rule.

Example:Day and/or Time Application
categorystring

Provider-defined category code for the rule.

Example:APP
textstringREQUIRED

The rule's full text, verbatim from the provider — display as-is, don't parse it.

Example:NO DAY/TIME TRAVEL RESTRICTIONS
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:validation_error
messagestringREQUIRED

A human-readable error description.

Example:'value' is not a valid offer_id
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:not_implemented_error
messagestringREQUIRED

A human-readable error description.

Example:Method not implemented for current provider
[
  {
    "route": "WAW-FRA",
    "category": "APP",
    "name": "Day and/or Time Application",
    "text": "NO DAY/TIME TRAVEL RESTRICTIONS"
  }
]
{
  "error": {
    "code": "validation_error",
    "message": "'value' is not a valid offer_id"
  }
}
{
  "error": {
    "code": "not_implemented_error",
    "message": "Method not implemented for current provider"
  }
}

Shop Offer Ancillaries

GEThttps://sandbox-api.drct.aero/offers/{id}/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

ancillariesarray of objectsREQUIRED

The ancillary services available for the order.

errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:validation_error
messagestringREQUIRED

A human-readable error description.

Example:'value' is not a valid offer_id
{
  "ancillaries": [
    {
      "id": "STB1",
      "name": "Checked Bag 20 KG",
      "type": "baggage",
      "price": { "amount": 32.0, "currency": "EUR" },
      "max_quantity": 2,
      "segments": ["SEG_M_1"],
      "passengers": ["PAX_1"]
    }
  ]
}
{
  "error": {
    "code": "validation_error",
    "message": "'value' is not a valid offer_id"
  }
}

Create Order

POSThttps://sandbox-api.drct.aero/orders

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.

Body parameters
offer_idstringREQUIRED

The priced offer you are booking. Expired offers return offer_not_found.

passengersarray of objects · 1–9REQUIRED

Reuse the id and type returned by search or pricing.

>= 1 items
ancillariesarray of objects

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

idstringREQUIRED

Identifies this order.

Example:6e5257be-d8d4-484f-8db0-922df4c8402a
channelstringREQUIRED

Name of the channel this order was booked through.

Example:Lufthansa Group
locatorstringREQUIRED

The PNR locator supplied by the provider — what you'd read out to the passenger.

Example:C57I60
statusstringREQUIRED

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.

Allowed values:newpendingvoidedissuedrefundeddeleted
airline_locatorsarray of objects

PNR locators supplied directly by the airlines involved, when different from the top-level locator.

cancel_beforedate-time

If the order isn't issued by this time, it's deleted automatically.

Example:2022-10-08T11:44:45.000+03:00
price_expires_atdate-time

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.

Example:2022-10-08T11:44:45.000+03:00
passengersarray of objects

The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).

>= 1 items
flightsarray of objects

The journeys booked on this order, each with its own fully-detailed segments.

>= 1 items
faresarray of objects

The fare rules and cabin/baggage conditions for this booking.

>= 1 items
phonesarray of objects

Contact phone numbers collected for this order.

>= 1 items
emailsarray of objects

Contact email addresses collected for this order.

>= 1 items
ticketsarray of objects

Electronic tickets issued for this order, one per passenger per fare.

documentsarray of objects

Identity documents collected for the passengers on this order.

ancillariesarray of objects

Ancillary services confirmed on this order.

seat_mapsarray of objects

Seats confirmed on this order, see the Seat Maps guide.

emdsarray of objects

Electronic documents (EMDs) issued on this order — e.g. a confirmed seat or a post-booking ancillary, once ticketed.

priceobject

Total price for the order.

price_detailsarray of objects

Price broken down by passenger type.

deleted_atdate-time

When the order was deleted, if it has been.

Example:2022-10-07T12:32:21+03:00
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:validation_error
messagestringREQUIRED

A human-readable error description.

Example:{'passengers':{'0':{'document':['is missing']}}}

Creating an order does not hold the price. Between create and issue the channel may reprice — handle price_changed.

Request
curl -X POST https://sandbox-api.drct.aero/orders \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "offer_id": "LH_6322c0cc1af833380f7e7bc5",
    "passengers": [
      {
        "id": "PAX1",
        "type": "ADT",
        "individual": {
          "first_name": "James",
          "last_name": "Watson",
          "title": "Mr",
          "gender": "M",
          "date_of_birth": "1990-10-01"
        },
        "phone": "+442079460000",
        "email": "james@example.com",
        "document": {
          "number": "AA234567",
          "gender": "M",
          "citizenship": "UA",
          "country_of_issue": "UA",
          "expiration_date": "2030-09-11"
        }
      }
    ]
  }'
{
  "id": "6e5257be-d8d4-484f-8db0-922df4c8402a",
  "status": "new",
  "channel": "Lufthansa Group",
  "locator": "C57I60",
  "cancel_before": "2026-09-28T11:44:45+03:00",
  "price": { "amount": 186.9, "currency": "EUR" },
  "passengers": [
    {
      "id": "PAX1",
      "type": "ADT",
      "individual": {
        "first_name": "James",
        "last_name": "Watson",
        "title": "Mr",
        "gender": "M",
        "date_of_birth": "1990-10-01"
      }
    }
  ],
  "tickets": []
}
{
  "error": {
    "code": "validation_error",
    "message": "{'passengers':{'0':{'document':['is missing']}}}"
  }
}

Issue Order

POSThttps://sandbox-api.drct.aero/orders/{id}/issue

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.

reshopboolean

Send true to accept a new price after a price_changed error. With false the call fails instead of charging a different amount.

Default:false
form_of_paymentobject

type: cash (default, settled against your DRCT balance) or credit_card with holder, number, expiry and CVV. Card payment is enabled per channel.

typestringREQUIRED

Type of payment source.

Allowed values:cashcredit_cardDefault:cash
credit_cardobject

Credit card information to make payment. Should be passed only if type is credit_card. Currently available for specific channels.

Response fields

idstringREQUIRED

Identifies this order.

Example:6e5257be-d8d4-484f-8db0-922df4c8402a
channelstringREQUIRED

Name of the channel this order was booked through.

Example:Lufthansa Group
locatorstringREQUIRED

The PNR locator supplied by the provider — what you'd read out to the passenger.

Example:C57I60
statusstringREQUIRED

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.

Allowed values:newpendingvoidedissuedrefundeddeleted
airline_locatorsarray of objects

PNR locators supplied directly by the airlines involved, when different from the top-level locator.

cancel_beforedate-time

If the order isn't issued by this time, it's deleted automatically.

Example:2022-10-08T11:44:45.000+03:00
price_expires_atdate-time

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.

Example:2022-10-08T11:44:45.000+03:00
passengersarray of objects

The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).

>= 1 items
flightsarray of objects

The journeys booked on this order, each with its own fully-detailed segments.

>= 1 items
faresarray of objects

The fare rules and cabin/baggage conditions for this booking.

>= 1 items
phonesarray of objects

Contact phone numbers collected for this order.

>= 1 items
emailsarray of objects

Contact email addresses collected for this order.

>= 1 items
ticketsarray of objects

Electronic tickets issued for this order, one per passenger per fare.

documentsarray of objects

Identity documents collected for the passengers on this order.

ancillariesarray of objects

Ancillary services confirmed on this order.

seat_mapsarray of objects

Seats confirmed on this order, see the Seat Maps guide.

emdsarray of objects

Electronic documents (EMDs) issued on this order — e.g. a confirmed seat or a post-booking ancillary, once ticketed.

priceobject

Total price for the order.

price_detailsarray of objects

Price broken down by passenger type.

deleted_atdate-time

When the order was deleted, if it has been.

Example:2022-10-07T12:32:21+03:00
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:not_found
messagestringREQUIRED

A human-readable error description.

Example:We have not found such a resource.
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:price_changed
messagestringREQUIRED

A human-readable error description.

Example:{'old':186.9,'new':191.4,'currency':'EUR'}

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.

Request
curl -X POST https://sandbox-api.drct.aero/orders/6e5257be-d8d4-484f-8db0-922df4c8402a/issue \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -d '{
    "reshop": false,
    "form_of_payment": { "type": "cash" }
  }'
curl -X POST https://sandbox-api.drct.aero/orders/6e5257be-d8d4-484f-8db0-922df4c8402a/issue \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -d '{
    "reshop": false,
    "form_of_payment": {
      "type": "credit_card",
      "credit_card": {
        "holder": "John Smith",
        "number": "5577810000000004",
        "exp_month": "07",
        "exp_year": "2029",
        "cvv": "033"
      }
    }
  }'
{
  "id": "6e5257be-d8d4-484f-8db0-922df4c8402a",
  "status": "issued",
  "locator": "C57I60",
  "tickets": [
    {
      "number": "2205740877684",
      "passenger": "PAX1",
      "status": "TICKETED",
      "nett_total": 176.9,
      "commission": 10
    }
  ]
}
{
  "error": {
    "code": "not_found",
    "message": "We have not found such a resource."
  }
}
{
  "error": {
    "code": "price_changed",
    "message": "{'old':186.9,'new':191.4,'currency':'EUR'}"
  }
}

Get Order

GEThttps://sandbox-api.drct.aero/orders/{id}

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.

newissuedvoidedrefundeddeleted

Response fields

idstringREQUIRED

Identifies this order.

Example:6e5257be-d8d4-484f-8db0-922df4c8402a
channelstringREQUIRED

Name of the channel this order was booked through.

Example:Lufthansa Group
locatorstringREQUIRED

The PNR locator supplied by the provider — what you'd read out to the passenger.

Example:C57I60
statusstringREQUIRED

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.

Allowed values:newpendingvoidedissuedrefundeddeleted
airline_locatorsarray of objects

PNR locators supplied directly by the airlines involved, when different from the top-level locator.

cancel_beforedate-time

If the order isn't issued by this time, it's deleted automatically.

Example:2022-10-08T11:44:45.000+03:00
price_expires_atdate-time

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.

Example:2022-10-08T11:44:45.000+03:00
passengersarray of objects

The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).

>= 1 items
flightsarray of objects

The journeys booked on this order, each with its own fully-detailed segments.

>= 1 items
faresarray of objects

The fare rules and cabin/baggage conditions for this booking.

>= 1 items
phonesarray of objects

Contact phone numbers collected for this order.

>= 1 items
emailsarray of objects

Contact email addresses collected for this order.

>= 1 items
ticketsarray of objects

Electronic tickets issued for this order, one per passenger per fare.

documentsarray of objects

Identity documents collected for the passengers on this order.

ancillariesarray of objects

Ancillary services confirmed on this order.

seat_mapsarray of objects

Seats confirmed on this order, see the Seat Maps guide.

emdsarray of objects

Electronic documents (EMDs) issued on this order — e.g. a confirmed seat or a post-booking ancillary, once ticketed.

priceobject

Total price for the order.

price_detailsarray of objects

Price broken down by passenger type.

deleted_atdate-time

When the order was deleted, if it has been.

Example:2022-10-07T12:32:21+03:00
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:not_found
messagestringREQUIRED

A human-readable error description.

Example:We have not found such a resource.
{
  "id": "6e5257be-d8d4-484f-8db0-922df4c8402a",
  "status": "issued",
  "locator": "C57I60",
  "airline_locators": [
    { "locator": "C57I60", "carrier": "LH" }
  ],
  "flights": [ /* segments with times, terminals */ ],
  "ancillaries": [
    {
      "name": "Bag 20 KG",
      "type": "baggage",
      "passenger": "PAX_1",
      "status": "issued"
    }
  ],
  "deleted_at": null
}
{
  "error": {
    "code": "not_found",
    "message": "We have not found such a resource."
  }
}

List Orders

GEThttps://sandbox-api.drct.aero/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.

idstringREQUIRED

Identifies this order.

Example:6e5257be-d8d4-484f-8db0-922df4c8402a
channelstringREQUIRED

Name of the channel this order was booked through.

Example:Lufthansa Group
locatorstringREQUIRED

The PNR locator supplied by the provider — what you'd read out to the passenger.

Example:C57I60
statusstringREQUIRED

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.

Allowed values:newpendingvoidedissuedrefundeddeleted
airline_locatorsarray of objects

PNR locators supplied directly by the airlines involved, when different from the top-level locator.

cancel_beforedate-time

If the order isn't issued by this time, it's deleted automatically.

Example:2022-10-08T11:44:45.000+03:00
price_expires_atdate-time

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.

Example:2022-10-08T11:44:45.000+03:00
passengersarray of objects

The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).

>= 1 items
flightsarray of objects

The journeys booked on this order, each with its own fully-detailed segments.

>= 1 items
faresarray of objects

The fare rules and cabin/baggage conditions for this booking.

>= 1 items
phonesarray of objects

Contact phone numbers collected for this order.

>= 1 items
emailsarray of objects

Contact email addresses collected for this order.

>= 1 items
ticketsarray of objects

Electronic tickets issued for this order, one per passenger per fare.

documentsarray of objects

Identity documents collected for the passengers on this order.

ancillariesarray of objects

Ancillary services confirmed on this order.

seat_mapsarray of objects

Seats confirmed on this order, see the Seat Maps guide.

emdsarray of objects

Electronic documents (EMDs) issued on this order — e.g. a confirmed seat or a post-booking ancillary, once ticketed.

priceobject

Total price for the order.

price_detailsarray of objects

Price broken down by passenger type.

deleted_atdate-time

When the order was deleted, if it has been.

Example:2022-10-07T12:32:21+03:00
200 OK
[
  {
    "id": "6e5257be-d8d4-484f-8db0-922df4c8402a",
    "status": "issued",
    "locator": "C57I60",
    "airline_locators": [
      { "locator": "C57I60", "carrier": "LH" }
    ],
    "flights": [ /* segments with times, terminals */ ],
    "deleted_at": null
  },
  { /* … one object per order, same shape as Get Order */ }
]

Reprice Order

GEThttps://sandbox-api.drct.aero/orders/{id}/reprice

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.

Response fields

old_priceobject

The order's price before this re-quote.

new_priceobject

The freshly quoted price — pass this to issue if price_changed is true.

price_changedbooleanREQUIRED

Whether new_price actually differs from old_price.

Example:true
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:not_found
messagestringREQUIRED

A human-readable error description.

Example:We have not found such a resource.
Request
curl https://sandbox-api.drct.aero/orders/6e5257be-d8d4-484f-8db0-922df4c8402a/reprice \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "DRCT-Version: 2026-09-01"
{
  "old_price": { "amount": 182.40, "currency": "EUR" },
  "new_price": { "amount": 198.90, "currency": "EUR" },
  "price_changed": true
}
{
  "error": {
    "code": "not_found",
    "message": "We have not found such a resource."
  }
}

Order Fare Rules

GEThttps://sandbox-api.drct.aero/orders/{id}/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

routestringREQUIRED

The departure–arrival airport pair this rule applies to.

Example:FRA-CDG
namestringREQUIRED

Short, provider-defined name of the rule.

Example:Day and/or Time Application
categorystring

Provider-defined category code for the rule.

Example:APP
textstringREQUIRED

The rule's full text, verbatim from the provider — display as-is, don't parse it.

Example:NO DAY/TIME TRAVEL RESTRICTIONS
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:not_found
messagestringREQUIRED

A human-readable error description.

Example:We have not found such a resource.
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:not_implemented_error
messagestringREQUIRED

A human-readable error description.

Example:Method not implemented for current provider
[
  {
    "route": "FRA-CDG",
    "category": "APP",
    "name": "Day and/or Time Application",
    "text": "NO DAY/TIME TRAVEL RESTRICTIONS"
  }
]
{
  "error": {
    "code": "not_found",
    "message": "We have not found such a resource."
  }
}
{
  "error": {
    "code": "not_implemented_error",
    "message": "Method not implemented for current provider"
  }
}

Cancel Order

DELhttps://sandbox-api.drct.aero/orders/{id}

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.

Response fields

idstringREQUIRED

Identifies this order.

Example:6e5257be-d8d4-484f-8db0-922df4c8402a
channelstringREQUIRED

Name of the channel this order was booked through.

Example:Lufthansa Group
locatorstringREQUIRED

The PNR locator supplied by the provider — what you'd read out to the passenger.

Example:C57I60
statusstringREQUIRED

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.

Allowed values:newpendingvoidedissuedrefundeddeleted
airline_locatorsarray of objects

PNR locators supplied directly by the airlines involved, when different from the top-level locator.

cancel_beforedate-time

If the order isn't issued by this time, it's deleted automatically.

Example:2022-10-08T11:44:45.000+03:00
price_expires_atdate-time

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.

Example:2022-10-08T11:44:45.000+03:00
passengersarray of objects

The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).

>= 1 items
flightsarray of objects

The journeys booked on this order, each with its own fully-detailed segments.

>= 1 items
faresarray of objects

The fare rules and cabin/baggage conditions for this booking.

>= 1 items
phonesarray of objects

Contact phone numbers collected for this order.

>= 1 items
emailsarray of objects

Contact email addresses collected for this order.

>= 1 items
ticketsarray of objects

Electronic tickets issued for this order, one per passenger per fare.

documentsarray of objects

Identity documents collected for the passengers on this order.

ancillariesarray of objects

Ancillary services confirmed on this order.

seat_mapsarray of objects

Seats confirmed on this order, see the Seat Maps guide.

emdsarray of objects

Electronic documents (EMDs) issued on this order — e.g. a confirmed seat or a post-booking ancillary, once ticketed.

priceobject

Total price for the order.

price_detailsarray of objects

Price broken down by passenger type.

deleted_atdate-time

When the order was deleted, if it has been.

Example:2022-10-07T12:32:21+03:00
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:not_found
messagestringREQUIRED

A human-readable error description.

Example:We have not found such a resource.
Request
curl -X DELETE https://sandbox-api.drct.aero/orders/6e5257be-d8d4-484f-8db0-922df4c8402a \
  -H "Authorization: Bearer $DRCT_API_KEY"
{
  "id": "6e5257be-d8d4-484f-8db0-922df4c8402a",
  "status": "deleted",
  "locator": "C57I60",
  "deleted_at": "2026-09-27T12:32:21+03:00"
}
{
  "error": {
    "code": "not_found",
    "message": "We have not found such a resource."
  }
}

Quote Order Refund

GEThttps://sandbox-api.drct.aero/orders/{id}/refund_quote

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

locatorstringREQUIRED

The PNR locator supplied by the provider.

Example:C57I60
totalobjectREQUIRED

What lands back on your balance — the penalty is already deducted from it, don't subtract it again.

penaltyobjectREQUIRED

What the airline keeps. Zero when the fare is fully refundable.

fareobject

The fare half of total. Omitted when the provider doesn't break the refund down.

taxesobject

The taxes half of total. Omitted when the provider doesn't break the refund down.

errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:not_found
messagestringREQUIRED

A human-readable error description.

Example:We have not found such a resource.
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:refund_unavailable
messagestringREQUIRED

A human-readable error description.

Example:Refund is not available for this channel.
Request
curl https://sandbox-api.drct.aero/orders/6e5257be-d8d4-484f-8db0-922df4c8402a/refund_quote \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "DRCT-Version: 2026-09-01"
{
  "locator": "C57I60",
  "total": { "amount": 350.00, "currency": "EUR" },
  "penalty": { "amount": 100.00, "currency": "EUR" },
  "fare": { "amount": 300.00, "currency": "EUR" },
  "taxes": { "amount": 50.00, "currency": "EUR" }
}
{
  "error": {
    "code": "not_found",
    "message": "We have not found such a resource."
  }
}
{
  "error": {
    "code": "refund_unavailable",
    "message": "Refund is not available for this channel."
  }
}

Refund Order

POSThttps://sandbox-api.drct.aero/orders/{id}/refund

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

idstringREQUIRED

Identifies the order this refund belongs to.

Example:6e5257be-d8d4-484f-8db0-922df4c8402a
locatorstringREQUIRED

The PNR locator supplied by the provider.

Example:C57I60
channelstringREQUIRED

Name of the channel this order was booked through.

Example:Turkish
statusstringREQUIRED

The order's status after confirming the refund — the documents below may still be processing individually.

Example:refunded
totalobject

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.

penaltyobject

The total penalty held back, on the same terms as total.

ticketsarray of objectsREQUIRED

Every ticket the refund covered.

emdsarray of objects

Refunded EMDs — present only when the order had ancillaries.

errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:not_found
messagestringREQUIRED

A human-readable error description.

Example:We have not found such a resource.
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:refund_not_allowed
messagestringREQUIRED

A human-readable error description.

Example:Refund is not permitted for the order.
Request
curl -X POST https://sandbox-api.drct.aero/orders/6e5257be-d8d4-484f-8db0-922df4c8402a/refund \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "DRCT-Version: 2026-09-01"
{
  "id": "6e5257be-d8d4-484f-8db0-922df4c8402a",
  "locator": "C57I60",
  "channel": "Turkish",
  "status": "refunded",
  "total": { "amount": 350.00, "currency": "EUR" },
  "penalty": { "amount": 100.00, "currency": "EUR" },
  "tickets": [
    {
      "number": "2205740877684",
      "passenger": "PAX1",
      "status": "REFUNDED",
      "total": { "amount": 350.00, "currency": "EUR" },
      "penalty": { "amount": 100.00, "currency": "EUR" }
    }
  ]
}
{
  "error": {
    "code": "not_found",
    "message": "We have not found such a resource."
  }
}
{
  "error": {
    "code": "refund_not_allowed",
    "message": "Refund is not permitted for the order."
  }
}

Ancillaries

GEThttps://sandbox-api.drct.aero/orders/{id}/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

ancillariesarray of objectsREQUIRED

The ancillary services available for the order.

errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:not_found
messagestringREQUIRED

A human-readable error description.

Example:We have not found such a resource.
{
  "ancillaries": [
    {
      "id": "ANCILLARY_42622140D81C-0DG-5",
      "name": "Bag 20 KG",
      "type": "baggage",
      "price": { "amount": 35.0, "currency": "EUR" },
      "max_quantity": 2,
      "segments": ["SEG_M_1"],
      "passengers": ["PAX_1"]
    }
  ]
}
{
  "error": {
    "code": "not_found",
    "message": "We have not found such a resource."
  }
}

Confirm Ancillary

POSThttps://sandbox-api.drct.aero/orders/{id}/ancillaries

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.

Body parameters
ancillariesarray of objectsREQUIRED

One item per ancillary being confirmed. Batch several — for different passengers or ancillaries — in a single request.

idstringREQUIRED

Ancillary id obtained from the Shop Ancillaries response.

Example:ANCILLARY_42622140D81C4F8DBFCD3A724E0BA4EE-0DG-5
passenger_idstringREQUIRED

Passenger id from the order.

Example:PAX_1
quantityinteger

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.

Example:1

Response fields

idstringREQUIRED

Identifies this order.

Example:6e5257be-d8d4-484f-8db0-922df4c8402a
channelstringREQUIRED

Name of the channel this order was booked through.

Example:Lufthansa Group
locatorstringREQUIRED

The PNR locator supplied by the provider — what you'd read out to the passenger.

Example:C57I60
statusstringREQUIRED

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.

Allowed values:newpendingvoidedissuedrefundeddeleted
airline_locatorsarray of objects

PNR locators supplied directly by the airlines involved, when different from the top-level locator.

cancel_beforedate-time

If the order isn't issued by this time, it's deleted automatically.

Example:2022-10-08T11:44:45.000+03:00
price_expires_atdate-time

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.

Example:2022-10-08T11:44:45.000+03:00
passengersarray of objects

The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).

>= 1 items
flightsarray of objects

The journeys booked on this order, each with its own fully-detailed segments.

>= 1 items
faresarray of objects

The fare rules and cabin/baggage conditions for this booking.

>= 1 items
phonesarray of objects

Contact phone numbers collected for this order.

>= 1 items
emailsarray of objects

Contact email addresses collected for this order.

>= 1 items
ticketsarray of objects

Electronic tickets issued for this order, one per passenger per fare.

documentsarray of objects

Identity documents collected for the passengers on this order.

ancillariesarray of objects

Ancillary services confirmed on this order.

seat_mapsarray of objects

Seats confirmed on this order, see the Seat Maps guide.

emdsarray of objects

Electronic documents (EMDs) issued on this order — e.g. a confirmed seat or a post-booking ancillary, once ticketed.

priceobject

Total price for the order.

price_detailsarray of objects

Price broken down by passenger type.

deleted_atdate-time

When the order was deleted, if it has been.

Example:2022-10-07T12:32:21+03:00
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:not_found
messagestringREQUIRED

A human-readable error description.

Example:We have not found such a resource.
Confirm · request
curl -X POST https://sandbox-api.drct.aero/orders/6e5257be-d8d4-484f-8db0-922df4c8402a/ancillaries \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ancillaries": [
      { "id": "ANCILLARY_42622140D81C-0DG-5", "passenger_id": "PAX_1", "quantity": 1 }
    ]
  }'
{
  "id": "6e5257be-d8d4-484f-8db0-922df4c8402a",
  "status": "issued",
  "locator": "C57I60",
  "ancillaries": [
    {
      "id": "ANCILLARY_42622140D81C-0DG-5",
      "name": "Bag 20 KG",
      "type": "baggage",
      "price": { "amount": 35.0, "currency": "EUR" },
      "segments": ["SEG_M_1"],
      "passenger": "PAX_1",
      "status": "issued"
    }
  ]
}
{
  "error": {
    "code": "not_found",
    "message": "We have not found such a resource."
  }
}

View Seat Maps

GEThttps://sandbox-api.drct.aero/orders/{id}/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.

ChannelWhen seats can be added
Flayer K (Ryanair)On an unissued order, before issue
WizzAirOn an unissued order, before issue

If the channel doesn't support seat maps, both this request and Add Seats return channel_is_off.

Response fields

seat_mapsarray of objectsREQUIRED

One entry per passenger per segment.

errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:not_found
messagestringREQUIRED

A human-readable error description.

Example:We have not found such a resource.
{
  "seat_maps": [
    {
      "segment": "SEG_M_1",
      "passenger": "PAX_1",
      "cabin": "Economy",
      "rows": [
        {
          "number": 1,
          "seats": [
            {
              "designator": "1A",
              "available": true,
              "characteristics": [
                { "code": "E", "description": "Exit and emergency exit" },
                { "code": "L", "description": "Leg space seat" }
              ],
              "price": { "amount": 29.6, "currency": "EUR" }
            },
            {
              "designator": "1B",
              "available": false,
              "characteristics": [],
              "price": { "amount": 29.6, "currency": "EUR" }
            }
          ]
        }
      ]
    }
  ]
}
{
  "error": {
    "code": "not_found",
    "message": "We have not found such a resource."
  }
}

Add Seats

POSThttps://sandbox-api.drct.aero/orders/{id}/seat_maps

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.

Body parameters
seat_mapsarray of objectsREQUIRED

One item per seat you're assigning.

passenger_idstringREQUIRED

Passenger id from the order.

Example:PAX_1
segment_idstringREQUIRED

Segment id from the order / seat map.

Example:SEG_M_1
designatorstringREQUIRED

Seat designator from the View Seat Maps response.

Example:12A

Response fields

idstringREQUIRED

Identifies this order.

Example:6e5257be-d8d4-484f-8db0-922df4c8402a
channelstringREQUIRED

Name of the channel this order was booked through.

Example:Lufthansa Group
locatorstringREQUIRED

The PNR locator supplied by the provider — what you'd read out to the passenger.

Example:C57I60
statusstringREQUIRED

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.

Allowed values:newpendingvoidedissuedrefundeddeleted
airline_locatorsarray of objects

PNR locators supplied directly by the airlines involved, when different from the top-level locator.

cancel_beforedate-time

If the order isn't issued by this time, it's deleted automatically.

Example:2022-10-08T11:44:45.000+03:00
price_expires_atdate-time

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.

Example:2022-10-08T11:44:45.000+03:00
passengersarray of objects

The travellers this applies to, each with a stable id you'll reference in later steps (pricing, booking).

>= 1 items
flightsarray of objects

The journeys booked on this order, each with its own fully-detailed segments.

>= 1 items
faresarray of objects

The fare rules and cabin/baggage conditions for this booking.

>= 1 items
phonesarray of objects

Contact phone numbers collected for this order.

>= 1 items
emailsarray of objects

Contact email addresses collected for this order.

>= 1 items
ticketsarray of objects

Electronic tickets issued for this order, one per passenger per fare.

documentsarray of objects

Identity documents collected for the passengers on this order.

ancillariesarray of objects

Ancillary services confirmed on this order.

seat_mapsarray of objects

Seats confirmed on this order, see the Seat Maps guide.

emdsarray of objects

Electronic documents (EMDs) issued on this order — e.g. a confirmed seat or a post-booking ancillary, once ticketed.

priceobject

Total price for the order.

price_detailsarray of objects

Price broken down by passenger type.

deleted_atdate-time

When the order was deleted, if it has been.

Example:2022-10-07T12:32:21+03:00
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:not_found
messagestringREQUIRED

A human-readable error description.

Example:We have not found such a resource.
errorobjectREQUIRED

The single error this response carries.

codestringREQUIRED

An identifier of an error type inside the API — branch on this, never on the message text.

Example:validation_error
messagestringREQUIRED

A human-readable error description.

Example:{"seat_maps":["must be filled"]}
Add Seats · request
curl -X POST https://sandbox-api.drct.aero/orders/6e5257be-d8d4-484f-8db0-922df4c8402a/seat_maps \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "seat_maps": [
      { "passenger_id": "PAX_1", "segment_id": "SEG_M_1", "designator": "12A" }
    ]
  }'
{
  "id": "6e5257be-d8d4-484f-8db0-922df4c8402a",
  "status": "issued",
  "locator": "C57I60",
  "seat_maps": [
    {
      "passenger": "PAX_1",
      "segment": "SEG_M_1",
      "designator": "12A",
      "price": { "amount": 29.6, "currency": "EUR" },
      "status": "issued"
    }
  ]
}
{
  "error": {
    "code": "not_found",
    "message": "We have not found such a resource."
  }
}
{
  "error": {
    "code": "validation_error",
    "message": "{\"seat_maps\":[\"must be filled\"]}"
  }
}

List Reporting Tickets

GEThttps://sandbox-api.drct.aero/reporting/tickets

Returns every ticket from issued orders for back-office reconciliation — nett totals, commission, ticketing office and last transaction time. JSON or XML.

Query parameters
date_fromdate-time

Defaults to the start of today. Filters by last transaction time.

Example:2022-09-16T00:00:00+03:00
date_todate-time

Defaults to the end of today.

Example:2022-09-16T23:59:59+03:00
numberstring

Ticket number. Used only when locator is blank.

Example:5661234567890
locatorstring

Booking reference (PNR). Takes precedence over number and the date range.

Example:ABCDE12

Response fields

idstringREQUIRED

Identifies this ticket.

Example:fca3a393-1f4b-4fd3-952b-a4a5df3fe61d
numberstringREQUIRED

The electronic document number issued by the airline.

Example:2205740877684
statusstringREQUIRED

Current document status — changes as operations are applied to it. refund means a confirmed refund covered this ticket — see the Refund guide.

Allowed values:issuedvoidreissuedrefundexchanged
locatorstringREQUIRED

The PNR locator supplied by the provider — what you'd read out to the passenger.

typestringREQUIRED

Ticket or EMD (electronic miscellaneous document).

Allowed values:TicketEMD
booked_atdate-timeREQUIRED

When the order this ticket belongs to was created.

Example:2022-10-07T12:32:21+03:00
last_transaction_atdate-timeREQUIRED

When this ticket's status last changed.

Example:2022-10-07T12:32:21+03:00
total_priceobjectREQUIRED

Total price of the ticket.

fareobjectREQUIRED

The fare portion of the price, excluding taxes.

taxesobject

The tax portion of the price, excluding fare.

commissionnumber

Agent commission applied to this document.

Example:10
commission_equivalentnumber

Agent commission applied to this document, converted to the account's settlement currency.

Example:10
originatorobjectREQUIRED

The user who issued this document.

companyobjectREQUIRED

The company this document was issued under.

ticketing_officestring

Office id that issued the ticket.

Example:AH89
booking_officestring

Office id that created the order.

Example:AH89
channelstringREQUIRED

Name of the channel this order was booked through.

Example:Lufthansa Group
passsengerobjectREQUIRED

The passenger this document was issued for.

segmentsarray of objectsREQUIRED

The flown legs covered by this document.

Request
curl -G https://sandbox-api.drct.aero/reporting/tickets \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -d "date_from=2026-07-01T00:00:00+03:00" \
  -d "date_to=2026-07-31T23:59:59+03:00"
200 OK
[
  {
    "id": "fca3a393-1f4b-4fd3-952b-a4a5df3fe61d",
    "number": "2205740877684",
    "status": "issued",
    "locator": "C57I60",
    "type": "Ticket",
    "booked_at": "2026-07-07T12:32:21+03:00",
    "last_transaction_at": "2026-07-07T12:32:21+03:00",
    "total_price": { "amount": 186.9, "currency": "EUR" },
    "fare": { "amount": 176.9, "currency": "EUR" },
    "taxes": { "amount": 10.0, "currency": "EUR" },
    "commission": 10,
    "originator": { "id": "ZZ7YQ6", "name": "John Watson" },
    "company": { "id": "ZZ7YQ6", "name": "Travel Gods" },
    "channel": "Lufthansa Group",
    "passsenger": { "first_name": "James", "last_name": "Watson", "type": "ADT" },
    "segments": [ /* … */ ]
  }
]

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.

Request
curl --compressed -X POST "https://sandbox-api.drct.aero/orders" \
  -H "Accept: application/json" \
  -H "Accept-Encoding: gzip" \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "DRCT-Version: 2026-09-01" \
  -d '{
    "offer_id": "'"$OFFER_ID"'",
    "passengers": [
      {
        "id": "PAX1",
        "type": "ADT",
        "individual": {
          "title": "Mr",
          "first_name": "James",
          "last_name": "Watson",
          "date_of_birth": "1990-10-01"
        },
        "phone": "+14840000000",
        "email": "example@mail.com",
        "document": {
          "number": "AA9999",
          "gender": "M",
          "citizenship": "UA",
          "country_of_issue": "UA",
          "expiration_date": "2023-09-08"
        }
      }
    ]
  }'

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 👉

Request · issue
curl -X POST "https://sandbox-api.drct.aero/orders/$ORDER_ID/issue" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "DRCT-Version: 2026-09-01"

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.

Error · price_changed
{
  "error": {
    "code": "price_changed",
    "message": "{'old':100.0,'new':105.0,'currency':'USD'}"
  }
}
Request · reshop
curl -X POST "https://sandbox-api.drct.aero/orders/$ORDER_ID/issue" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "DRCT-Version: 2026-09-01" \
  -d '{ "reshop": true }'

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:

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 👉

Request
curl -X DELETE "https://sandbox-api.drct.aero/orders/$ORDER_ID" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "DRCT-Version: 2026-09-01"

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.

GET /orders/{id}/refund_quote → what you would get back
POST /orders/{id}/refund → take it

⚠️ 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.

Request · quote
curl -X GET https://sandbox-api.drct.aero/orders/$ORDER_ID/refund_quote \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "DRCT-Version: 2026-09-01"
Response
{
  "locator": "C57I60",
  "total": { "amount": 350.00, "currency": "EUR" },
  "penalty": { "amount": 100.00, "currency": "EUR" },
  "fare": { "amount": 300.00, "currency": "EUR" },
  "taxes": { "amount": 50.00, "currency": "EUR" }
}

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".

Request · confirm
curl -X POST https://sandbox-api.drct.aero/orders/$ORDER_ID/refund \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "DRCT-Version: 2026-09-01"
Response
{
  "id": "6e5257be-d8d4-484f-8db0-922df4c8402a",
  "locator": "C57I60",
  "channel": "Turkish",
  "status": "refunded",
  "total": { "amount": 350.00, "currency": "EUR" },
  "penalty": { "amount": 100.00, "currency": "EUR" },
  "tickets": [
    {
      "number": "2205740877684",
      "passenger": "PAX1",
      "status": "REFUNDED",
      "total": { "amount": 350.00, "currency": "EUR" },
      "penalty": { "amount": 100.00, "currency": "EUR" }
    }
  ]
}

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.

{
  "status": "refunded",
  "tickets": [
    { "number": "2205740877684", "passenger": "PAX1", "status": "TICKETED" }
  ]
}

Errors

CodeWhen
refund_unavailableYou 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_foundThe 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

  1. Search offers (Search Offers).
  2. Take an offer id from the search response (flights_options[].offers[].id).
  3. Retrieve the upsell fares (GET /offers/:id/upsell).
  4. 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.

Request
curl -X GET "https://sandbox-api.drct.aero/offers/$OFFER_ID/upsell" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "DRCT-Version: 2026-09-01"

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.

ChannelWhen ancillaries can be addedEMD issuance
Flayer K (Ryanair)Before the order is issuedConfirmed together with the issued order
WizzAirBefore the order is issued — baggage and other services (priority boarding, check-in, special assistance)Confirmed together with the issued order
Lufthansa GroupAfter the order is issuedAsynchronous — see Pending Ancillaries
TurkishAfter the order is issuedSynchronous — issued immediately
FlyOneBefore the order exists — shopped on the offer via Shop Offer AncillariesConfirmed 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

  1. Create an order (Create Order).
  2. Issue the order (Issue Order) — if required.
  3. Retrieve available ancillaries (GET /orders/:id/ancillaries).
  4. 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.
Request · shop
curl -X GET "https://sandbox-api.drct.aero/orders/$ORDER_ID/ancillaries" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "DRCT-Version: 2026-09-01"
Response · shop
{
  "ancillaries": [
    {
      "id": "ANCILLARY_42622140D81C4F8DBFCD3A724E0BA4EE-0DG-5",
      "name": "Bag 20 KG",
      "type": "baggage",
      "segments": ["SEG_M_1"],
      "passengers": ["PAX_1"],
      "price": { "amount": 20.20, "currency": "EUR" }
    }
  ]
}

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.

Request · confirm
curl -X POST "https://sandbox-api.drct.aero/orders/$ORDER_ID/ancillaries" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "DRCT-Version: 2026-09-01" \
  -d '{
    "ancillaries": [
      { "id": "ANCILLARY_42622140D81C4F8DBFCD3A724E0BA4EE-0DG-5", "passenger_id": "PAX_1", "quantity": 1 }
    ]
  }'
Response · order.ancillaries
{
  "...": "...",
  "ancillaries": [
    {
      "id": "ANCILLARY_42622140D81C4F8DBFCD3A724E0BA4EE-0DG-5",
      "name": "Bag 20 KG",
      "type": "baggage",
      "passenger": "PAX_1",
      "segments": ["SEG_M_1"],
      "price": { "amount": 20.20, "currency": "EUR" },
      "status": "issued"
    }
  ]
}

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:

StatusMeaning
confirmedAdded to the order and confirmed by the provider; EMD not issued yet.
pendingAwaiting provider confirmation; EMD issuance in progress.
issuedThe ancillary EMD has been issued.
cancelledThe ancillary was cancelled / could not be issued.

How it works

  1. You confirm the ancillary (POST /orders/:id/ancillaries). On an asynchronous channel (Lufthansa Group) the returned ancillary has "status": "pending".
  2. Our system polls the provider and issues the EMD automatically once the ancillary is confirmed — no extra call is required from you.
  3. 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.

Request · poll order
curl -X GET "https://sandbox-api.drct.aero/orders/$ORDER_ID" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "DRCT-Version: 2026-09-01"
Response · still pending
{
  "...": "...",
  "ancillaries": [
    {
      "id": "ANCILLARY_42622140D81C4F8DBFCD3A724E0BA4EE-0DG-5",
      "name": "Bag 20 KG",
      "type": "baggage",
      "passenger": "PAX_1",
      "segments": ["SEG_M_1"],
      "price": { "amount": 20.20, "currency": "EUR" },
      "status": "pending"
    }
  ]
}

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.

ChannelWhen seats can be added
Flayer K (Ryanair)On an unissued order, before issue
WizzAirOn 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.
Response
{
  "seat_maps": [
    {
      "segment": "SEG_M_1",
      "passenger": "PAX_1",
      "cabin": "Economy",
      "rows": [
        {
          "number": 1,
          "seats": [
            {
              "designator": "1A",
              "available": true,
              "characteristics": [
                { "code": "E", "description": "Exit and emergency exit" },
                { "code": "L", "description": "Leg space seat" }
              ],
              "price": { "amount": 29.60, "currency": "EUR" }
            },
            {
              "designator": "1B",
              "available": false,
              "characteristics": [],
              "price": { "amount": 29.60, "currency": "EUR" }
            }
          ]
        }
      ]
    }
  ]
}

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:

CodeDescription
1Restricted seat - General
8No seat at this location
9Center seat (not window, not aisle)
AAisle seat
BSeat with bassinet facility
CHChargeable seat
EExit and emergency exit
FCFront of cabin class/compartment
HSeat with facilities for handicapped/incapacitated passenger
IESeat not suitable for child
LLeg space seat
LSLeft side of aircraft
MSeat without a movie view
MSMiddle seat
NNo smoking seat
OPreferential seat
OWOver wing seat(s)
RSRight side of aircraft
USeat suitable for unaccompanied minors
VSeat to be left vacant or offered last
WWindow seat
1ASeat not allowed for infant
1BSeat not allowed for medical
1CSeat not allowed for unaccompanied minor
1DRestricted recline seat
72Undesirable 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.
Request
{
  "seat_maps": [
    {
      "passenger_id": "PAX_1",
      "segment_id": "SEG_M_1",
      "designator": "12A"
    }
  ]
}
Response
{
  "...": "...",
  "seat_maps": [
    {
      "passenger": "PAX_1",
      "segment": "SEG_M_1",
      "designator": "12A",
      "price": { "amount": 29.60, "currency": "EUR" },
      "status": "issued"
    }
  ]
}

Errors

CodeWhen
channel_is_offThe channel does not support seat maps, or ticketing is off for your agency.
invalid_order_statusThe order status does not allow seats (e.g. already issued).
validation_errorThe seat_maps payload is invalid (empty list, or a bad designator).
not_foundThe 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.
{
  "id": "6e5257be-d8d4-484f-8db0-922df4c8402a",
  "status": "new",
  "channel": "Lufthansa Group",
  "cancel_before": "2026-09-12T18:00:00.000+03:00",
  "price_expires_at": "2026-09-11T18:00:00.000+03:00",
  "price": { "amount": 420.00, "currency": "EUR" },
  "...": "..."
}

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.
Request
curl "https://sandbox-api.drct.aero/orders/$ORDER_ID/reprice" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "DRCT-Version: 2026-09-01"
Response
{
  "old_price": { "amount": 420.00, "currency": "EUR" },
  "new_price": { "amount": 435.00, "currency": "EUR" },
  "price_changed": true
}

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.

Error
{
  "error": {
    "code": "price_changed",
    "message": "{'old':100.0,'new':105.0,'currency':'USD'}"
  }
}
Reissue with the new price
curl -X POST "https://sandbox-api.drct.aero/orders/$ORDER_ID/issue" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $DRCT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "DRCT-Version: 2026-09-01" \
  -d '{ "reshop": true }'

Errors

CodeWhen
price_changedYou tried to issue at a stale price without accepting the change. Re-issue with { "reshop": true }.
not_foundThe 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.

Charter cheat sheet
Carrier filter code
CSH
Provider support hours
6 a.m. – 3 p.m. UTC
Typical confirmation time
~15 minutes