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 book() 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. book() → 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.