> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dev.gojinko.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Ancillaries

> Baggage, seats, meals and other add-ons on a flight: how to read available_ancillaries and select them

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

<Note>
  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`.
</Note>

```typescript theme={null}
// 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 }],
})
```

<Note>
  **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`.
</Note>

## The cart item

```json theme={null}
{
  "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

| Field                  | Type                 | Notes                                                   |
| ---------------------- | -------------------- | ------------------------------------------------------- |
| `offer_id`             | string               | Pass this to `select_ancillaries`                       |
| `category`             | enum                 | See the 11 values below                                 |
| `label`                | string               | Human-readable, e.g. `"1 bag · 23kg"`                   |
| `description`          | string               | Longer description                                      |
| `price`                | Money                | `{ amount, currency, decimal_places }`; absent = free   |
| `scope`                | enum                 | Who/what it applies to (see below)                      |
| `max_quantity`         | integer              | Max units selectable                                    |
| `mutually_exclusive`   | string\[]            | `offer_id`s that conflict with this one                 |
| `segment_ref_ids`      | string\[]            | Segments it applies to                                  |
| `journey_ref_id`       | string               | Journey it applies to                                   |
| `applicable_pax_types` | string\[]            | e.g. `["ADT"]`                                          |
| `provider_metadata`    | `map<string,string>` | Pass-through provider data                              |
| `baggage_details`      | object               | Present when `category = BAGGAGE` / `CABIN_BAG_UPGRADE` |
| `seat_details`         | object               | Present when `category = SEAT`                          |
| `meal_details`         | object               | Present when `category = MEAL`                          |
| `assistance_details`   | object               | Present 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`

```json theme={null}
{ "pieces": 1, "weight_kg": 23, "weight_lb": 50, "dimensions": "158cm" }
```

### Seat: `seat_details`

```json theme={null}
{
  "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`

```json theme={null}
{
  "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`

```json theme={null}
"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"
  }
]
```

<Note>
  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.
</Note>

## Selecting ancillaries

**REST / CLI**: discrete operation:

```bash theme={null}
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.
