A package finalizes two or more services drawn from accommodation, transfer, tour, and charter flight as one booking with one inclusive price. Either every service confirms, or nothing does.
Flight components use the charter flight API. Connected-supplier airline fares booked in Console are not accepted by the Partner package API.
product searches → package/search → package/prebook → package/book
→ bookings/{id} → cancellation-quote → cancel
All package endpoints require the distribution:booking scope. Lifecycle
(read, quote, cancel) stays on the canonical /api/v1/bookings/{public_id}
resource; there is no parallel package lifecycle URL family.
Step 1: Compose (POST /api/v1/package/search)
Package search is a composer over offers you already selected, not a discovery endpoint. Run the product searches first, pick the offers, then submit each product's canonical selection handle as a component:
travellers[]: 1–30 entries, each with a uniqueref(^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$), a type (ADULT/CHILD/INFANT) and a date of birth where the type requires it. Ages are resolved separately at each component's service date.components[]: 2–5 entries spanning at least two product types. Each carries itscomponent_ref,product_type, a non-emptytraveller_refssubset, and exactly one product-specific authority branch. Party is derived from the selected traveller references.- An accommodation component (
product_type: 1) carries the opaqueselection_tokencopied verbatim from single-property offer detail. That proof is the only caller-supplied authority: do not sendallocation_idor a reconstructedselectionalongside it. The composer verifies its sealed exact slots and context directly. - Transfer, tour, and flight components retain their search allocation's
allocation_idand exact product-specific replayselection. Theselectionis the search you ran, not the offer you picked. A transfer searched withservice_class: 0and no vehicle filter keeps both of those in itsselectioneven if the chosen offer reports a shared service class, and a tour searched across every modality keepsmodality_public_idabsent. Both fields are bound into the component'sallocation_id, so narrowing them here describes a different search, and the composer refuses the component with410 SOLD_OUTrather than substituting another offer. - One
currency_codeand onelanguagefor the whole package;jurisdiction_codeselects the organiser's compliance profile. Each non-accommodation selection repeats the same currency and language because both are part of its exact search replay boundary.languageis a BCP 47 tag such astr-TR; a malformed tag answers400, and the server compares canonical casing.
For example, an accommodation and transfer composition uses the sealed accommodation proof while retaining the transfer's replay fields:
{
"travellers": [
{ "ref": "adult-1", "kind": "ADULT" },
{ "ref": "adult-2", "kind": "ADULT" }
],
"components": [
{
"component_ref": "acc-1",
"product_type": 1,
"selection_token": "sel1.active.b3BhcXVl",
"traveller_refs": ["adult-1", "adult-2"]
},
{
"component_ref": "transfer-1",
"product_type": 2,
"allocation_id": "alc_9c1248a5e3d760bf21483a0c6e527d19",
"selection": {
"from_zone_public_id": "zn_Airport01",
"to_zone_public_id": "zn_Hotel77",
"direction": 1,
"service_date": "2026-06-10",
"service_class": 0,
"currency_code": "EUR",
"language": "en"
},
"traveller_refs": ["adult-1", "adult-2"]
}
],
"currency_code": "EUR",
"language": "en",
"jurisdiction_code": "TR"
}
The response returns ordered component offers, one inclusive
package_total_amount, the structured compliance disclosure block, and a
package_allocation_id (palc_<32hex>) whose stored composition is valid for
15 minutes, reported as expires_at. Nothing is ever silently dropped: a
component that cannot be composed comes back as an explicit error.
The package total includes exclusive tax collected with an accommodation component. A tax payable at the property remains separate. Use the returned package total without adding the component's tax breakdown again.
The package allocation is immutable. Adding, removing or swapping a
component means a new package/search and a new package allocation.
Step 2: Revalidate (POST /api/v1/package/prebook)
Send only the package_allocation_id. Every component is requoted from its
stored exact descriptor or product replay boundary:
- Unchanged →
match_status: MATCHED. - Any price difference at canonical currency precision, with no tolerance
band, produces
match_status: PRICE_CHANGEDwith refreshed per-item and total amounts and a newly signed token. - A mandatory or selected optional component that is no longer available →
SOLD_OUT. Components are never dropped for you.
The signed prebook_token expires in 30 minutes and pins the quote, the
item selection and the compliance disclosure version.
Step 3: Finalize (POST /api/v1/package/book)
Book requires your partner account to settle on account with the seller. Without
those terms it returns 422 PAYMENT_INTENT_NOT_SUPPORTED before writing the
booking. An optional payment_intent_kind may confirm the resolved INVOICE
intent; it cannot select different terms. Omit it to use your account's terms.
Search results and a prebook token do not replace this book-time check. See
Booking payment terms.
Each package component keeps its own payment obligations toward its supplier. A prepaid stay inside a package is not converted to pay at the property.
Requires the Idempotency-Key header (1–128 characters). Send the
prebook_token, the booking contact, named traveller details keyed by the
prebook travellers[].ref set (the keys must match exactly) and, when the
resolved compliance profile requires it, the disclosure delivery evidence.
There is no price field: book never reprices a signed snapshot. The booking confirms every service together, or fails without a partial booking.
The response is the canonical booking envelope with product_type: 5,
items[] (each with its own public_id), the booked inclusive total and the
aggregate cancellation summary.
Idempotency
Package book and package cancel are operation-scoped: the same key can
serve one book and one cancel without collision. Within one operation, the
Stripe-style contract from Idempotency applies: same key +
same payload replays the stored result; same key + different payload returns
409 IDEMPOTENCY_KEY_CONFLICT.
Reading and cancelling: canonical endpoints, item-aware
GET /api/v1/bookings/{public_id} returns the package with items[],
active_item_count, active_selling_rate_total_amount and
realized_penalty_total_amount.
POST /api/v1/bookings/{public_id}/cancellation-quote and
POST /api/v1/bookings/{public_id}/cancel accept an optional
item_public_ids[]:
- Omitted → the quote/cancel targets all live items.
- Present → 1–5 item public ids of that booking. Already-cancelled targets return an idempotent per-item result; non-cancellable targets are rejected per item while valid targets proceed.
- The aggregate penalty is the sum of the targeted items' penalties, each evaluated from its own frozen policy snapshot. Components are never netted against each other.
- Cancelling an accommodation item refunds its collected exclusive tax on top of any accommodation refund. The penalty excludes that exclusive tax, including for a non-refundable stay.
- Surviving items keep their booked prices. Partial cancellation never reprices what remains.
Package cancel requires the Idempotency-Key header; the per-item result set
is stored and replayed on retry.
Stable errors
| HTTP | Code |
|---|---|
| 404 | RATE_NOT_AVAILABLE, PACKAGE_COMPLIANCE_PROFILE_NOT_FOUND, BOOKING_NOT_FOUND, BOOKING_ITEM_NOT_FOUND |
| 409 | PREBOOK_EXPIRED, IDEMPOTENCY_KEY_CONFLICT, BOOKING_NOT_CANCELLABLE |
| 410 | SOLD_OUT |
| 422 | PACKAGE_COMPOSITION_INVALID, PACKAGE_PROVIDER_MISMATCH, PACKAGE_CURRENCY_MISMATCH, PACKAGE_COMPONENT_UNSUPPORTED, PACKAGE_TRAVELLER_INVALID, SELLING_RATE_BELOW_SUPPLIER_NET, SELLING_RATE_OVERRIDE_NOT_ENROLLED, SOURCE_MARKET_BLOCKED, GUEST_AGE_NOT_ACCEPTED, COMPLIANCE_DELIVERY_REQUIRED |
SOLD_OUT covers situations that read alike from outside. A counted allotment
can have run out; an accommodation proof's exact sealed slots can become
unavailable; or a transfer, tour, or flight replay can stop returning its
allocation_id. The message names which one because they need different
answers: the first asks the supplier for allotment, while the others mean the
selected component must be picked again.
GUEST_AGE_NOT_ACCEPTED means the accommodation component no longer accepts a
child of its party. See Guest age refusals.
All errors use the standard envelope from Errors.
Composition rules
- 2–5 components, at least two distinct product types; a package never contains another package.
- One provider's supply and one currency per package. Cross-provider composition and package-level FX (foreign exchange) are not supported.
- Every component carries at least one traveller; every traveller rides at least one component.
- Optional components can be removed during composition; once inside the signed token, they are part of the atomic book.
Not the same as a trip
Trip bundling groups bookings you already confirmed into
one itinerary. It is money-free: every member keeps its own total, its own
cancellation policy and its own lifecycle. A trip carries a link_type whose
value 1 is named Package because it records the regulatory relationship,
but grouping never creates a package contract. Only /package/book does.