Skip to main content
Ancillaries are the purchasable add-ons on a flight: extra baggage, seat selection, meals, priority boarding, lounge access, and more. They surface on each flight item in a quoted cart, and you select them before checkout.

How to list ancillaries (REST)

Ancillaries only exist after the flight is priced (the provider, TravelFusion, only emits the catalog at pricing time). They are not on the raw flight_search result, and you should not call checkout() to read them (that mints a checkout URL). Use the dedicated endpoint instead: it runs the price quote transparently and returns the catalog without generating a checkout URL:
GET /v1/trip/{trip_id}/ancillaries
Lifecycle:
  1. flight_search → live-priced offer
  2. trip (add item) → flight in the cart
  3. trip (set travelers), required; ancillaries are priced per passenger
  4. GET /v1/trip/{trip_id}/ancillaries → runs the quote behind the scenes and returns items[].available_ancillaries[]
  5. select_ancillaries → move chosen offers into selected_ancillaries[], updating total_with_ancillaries
  6. checkout() → checkout

Response: ready vs pricing

The quote runs upstream and can take a few seconds, so the call is two-state:
  • 200 { "status": "ready", "items": [...] }: the catalog is ready; read available_ancillaries off each item.
  • 202 { "status": "pricing", "retry_after_seconds": 2 } + a Retry-After header: the quote is still pricing; poll the same URL until it returns 200.
A completed quote is reused while valid (expires_at), so repeat calls return instantly. The response never contains a checkout_url.
The SDK exposes this as client.getAncillaries(tripId). The MCP trip tool surfaces the same available_ancillaries[] on the quoted trip item inside the widget flow. On the Jinko-hosted checkout page, ancillary selection is not currently exposed: build your own UI against available_ancillaries + select_ancillaries.
// Set travelers first, then list ancillaries (quote runs transparently).
let res = await client.getAncillaries(tripId)
while (res.status === 'pricing') {
  await new Promise(r => setTimeout(r, (res.retry_after_seconds ?? 2) * 1000))
  res = await client.getAncillaries(tripId)        // poll the same call
}
const bags = res.items[0].available_ancillaries.filter(a => a.type === 'bag')
await client.selectAncillaries({
  trip_id: tripId,
  item_id: res.items[0].item_id,
  selections: [{ offer_id: bags[0].offer_id }],
})
Two response shapes. The REST endpoint above returns a simplified offer per item: { offer_id, type, label, display_label, description, price_per_unit, per_pax, max_quantity } (where type is the lower-cased category, e.g. bag, seat, meal, assistance, cabin_bag_upgrade, sports_equipment, other). label is the raw provider string; display_label is a cleaned, human-readable version (HTML entities decoded, provider & component-joiners normalized to , ). Prefer it for display, falling back to label. The richer Ancillary object documented below (category, scope, *_details, …) is what the MCP trip tool returns on the quoted trip item. Both carry the same offer_id you pass to select_ancillaries.

The cart item

{
  "trip_item_token": "...",
  // ... flight fields ...
  "available_ancillaries": [ /* Ancillary[], see below */ ],
  "selected_ancillaries":  [ /* SelectedAncillary[] */ ],
  "total_with_ancillaries": { "amount": 48500, "currency": "EUR", "decimal_places": 2 }
}

The Ancillary object

FieldTypeNotes
offer_idstringPass this to select_ancillaries
categoryenumSee the 11 values below
labelstringHuman-readable, e.g. "1 bag · 23kg"
descriptionstringLonger description
priceMoney{ amount, currency, decimal_places }; absent = free
scopeenumWho/what it applies to (see below)
max_quantityintegerMax units selectable
mutually_exclusivestring[]offer_ids that conflict with this one
segment_ref_idsstring[]Segments it applies to
journey_ref_idstringJourney it applies to
applicable_pax_typesstring[]e.g. ["ADT"]
provider_metadatamap<string,string>Pass-through provider data
baggage_detailsobjectPresent when category = BAGGAGE / CABIN_BAG_UPGRADE
seat_detailsobjectPresent when category = SEAT
meal_detailsobjectPresent when category = MEAL
assistance_detailsobjectPresent when category = SPECIAL_ASSISTANCE
category (11 values): BAGGAGE, SEAT, MEAL, CABIN_BAG_UPGRADE, SPORTS_EQUIPMENT, PRIORITY_BOARDING, SPECIAL_ASSISTANCE, INSURANCE, LOUNGE, FREQUENT_FLYER, OTHER. scope (4 values): PER_PAX_PER_SEGMENT, PER_PAX_PER_JOURNEY, PER_PAX_PER_BOOKING, PER_BOOKING.

Baggage: baggage_details

{ "pieces": 1, "weight_kg": 23, "weight_lb": 50, "dimensions": "158cm" }

Seat: seat_details

{
  "flight_number": "AF1234",
  "seat_type": "STANDARD",
  "characteristics": ["WINDOW", "EXTRA_LEGROOM"],
  "available_seats": [
    {
      "seat_number": "12A",
      "seat_type": "STANDARD",
      "characteristics": ["WINDOW"],
      "csp_value": "...",
      "price": { "amount": 1500, "currency": "EUR", "decimal_places": 2 }
    }
  ]
}
For full seat-map rendering there is also a SeatMap shape (rows[] → seats[] with availability + features), but for ancillary purchase you use seat_details.available_seats.

Meal: meal_details

{
  "meal_code": "VGML", "meal_name": "Vegetarian",
  "available_meals": [
    { "meal_code": "VGML", "meal_name": "Vegetarian", "csp_value": "...",
      "price": { "amount": 1200, "currency": "EUR", "decimal_places": 2 } }
  ]
}

Example available_ancillaries

"available_ancillaries": [
  {
    "offer_id": "anc_bag_23",
    "category": "BAGGAGE",
    "label": "1 checked bag · 23kg",
    "price": { "amount": 4500, "currency": "EUR", "decimal_places": 2 },
    "scope": "PER_PAX_PER_SEGMENT",
    "max_quantity": 3,
    "segment_ref_ids": ["seg_1"],
    "applicable_pax_types": ["ADT"],
    "baggage_details": { "pieces": 1, "weight_kg": 23, "weight_lb": 50 }
  },
  {
    "offer_id": "anc_seat_12A",
    "category": "SEAT",
    "label": "Seat 12A (window)",
    "price": { "amount": 1500, "currency": "EUR", "decimal_places": 2 },
    "scope": "PER_PAX_PER_SEGMENT",
    "mutually_exclusive": ["anc_seat_12B"],
    "segment_ref_ids": ["seg_1"],
    "seat_details": {
      "flight_number": "AF1234",
      "available_seats": [
        { "seat_number": "12A", "seat_type": "STANDARD", "characteristics": ["WINDOW"],
          "price": { "amount": 1500, "currency": "EUR", "decimal_places": 2 } }
      ]
    }
  },
  {
    "offer_id": "anc_priority",
    "category": "PRIORITY_BOARDING",
    "label": "Priority boarding",
    "price": { "amount": 800, "currency": "EUR", "decimal_places": 2 },
    "scope": "PER_PAX_PER_BOOKING"
  }
]
Which ancillaries are returned depends entirely on the airline/provider: many fares return none, LCCs typically return baggage + seats. Always render defensively: any *_details block is optional and only present for its category.

Selecting ancillaries

REST / CLI: discrete operation:
POST /v1/select_ancillaries
{
  "trip_id": "trip_1048576",
  "item_id": "prod_abc123",
  "selections": [ { "offer_id": "anc_bag_23", "quantity": 1, "pax_ref_id": "pax_1" } ]
}
Pass the offer_id(s) you read from available_ancillaries. Returns the updated selected_ancillaries[] + total_with_ancillaries. (@gojinko/cli: jinko select-ancillaries.) MCP / plugin: ancillary selection is not a standalone tool; it’s an action of the umbrella trip tool. mcp-apps handles it inside the trip/book widget flow.