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

# Flight + Hotel booking

> One trip, two items, one Stripe checkout. Add a flight and a hotel to the same trip and book them together.

Most real journeys involve more than one item. Jinko's `trip` is a single booking unit: you can put a flight and a hotel into the same trip, set the travelers once, and check out once. The user pays a single Stripe charge and receives a single confirmation with a single Jinko booking reference covering both bookings.

This guide builds a Paris weekend (a flight from JFK to CDG and a hotel in Paris) end to end with side-by-side examples for SDK, CLI, and MCP.

The one-line version of the flow:

```
flight discovery → flight_search → hotel_search → trip(add_item flight + travelers) → trip(add_item hotel) → checkout → user pays → get_trip
```

## Prerequisites

* A Jinko account and an API key (`jnk_...`). [Get one](https://dashboard.gojinko.com/developers/keys).
* For the SDK path: Node.js 20 or later, then `npm install @gojinko/api-client`.
* For the CLI path: `npm install -g @gojinko/cli && jinko auth login --key jnk_...`.
* For the MCP path: any MCP client connected to `https://mcp.builders.gojinko.com/mcp`.

## 1) Discover the flight

Discover flights for the route and dates, then confirm pricing on the candidate the user picks. This is the same first two steps as the [Flight booking guide](/guides/flight-booking), so we keep it short.

<Tabs>
  <Tab title="SDK">
    ```typescript theme={null}
    import { createJinkoClient } from '@gojinko/api-client'

    const client = await createJinkoClient({ apiKey: process.env.JINKO_API_KEY })

    const { destinations } = await client.findDestination({
      origins: ['JFK'],
      destinations: ['CDG'],          // filter discovery to your target airport
      origin_type: 'airport',
      destination_type: 'airport',
      trip_type: 'roundtrip',
      departure_dates: ['2026-06-15'],
      return_dates: ['2026-06-22'],
    })

    // flight_search is flat: origin/destination/departure_date are required;
    // offer_token (optional) re-prices the discovered candidate. The returned
    // fare's trip_item_token is ready to add to a trip as-is.
    const priced = await client.flightSearch({
      origin: 'JFK',
      destination: 'CDG',
      departure_date: '2026-06-15',
      return_date: '2026-06-22',
      trip_type: 'roundtrip',
      offer_token: destinations[0].flights[0].offer_token,
    })
    const flightToken = priced.offers[0].fares[0].trip_item_token
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    FLIGHT_OFFER=$(jinko find-destination --origins JFK --destinations CDG \
      --origin-type airport --destination-type airport \
      --trip-type roundtrip --departure-dates 2026-06-15 --return-dates 2026-06-22 \
      --format json | jq -r '.destinations[0].flights[0].offer_token')

    FLIGHT_TOKEN=$(jinko flight-search \
      --origin JFK --destination CDG \
      --departure-date 2026-06-15 --return-date 2026-06-22 \
      --offer-token "$FLIGHT_OFFER" \
      --format json | jq -r '.offers[0].fares[0].trip_item_token')
    ```
  </Tab>

  <Tab title="MCP">
    > "From JFK, what can I book to Paris (CDG), leaving June 15 and returning June 22?"

    The agent calls `find_destination` to surface candidates. Pick one; it then calls `flight_search` to confirm the live price and surface fare options. The agent now has a flight `trip_item_token` ready to drop into a trip.
  </Tab>
</Tabs>

## 2) Find the hotel

Search hotels at the destination for the same dates. Hotel rates are already live offers, no separate price-check step is needed.

<Tabs>
  <Tab title="SDK">
    ```typescript theme={null}
    const hotels = await client.hotelSearch({
      destination: { query: 'Paris' },
      checkin: '2026-06-15',
      checkout: '2026-06-22',
      adults: 1,
    })

    const hotelToken = hotels.hotels[0].rooms[0].rates[0].offer_id // htl_*
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    HOTEL_TOKEN=$(jinko hotel-search \
      --destination '{"query":"Paris"}' \
      --checkin 2026-06-15 --checkout 2026-06-22 \
      --adults 1 \
      --format json | jq -r '.hotels[0].rooms[0].rates[0].offer_id')
    ```
  </Tab>

  <Tab title="MCP">
    > "And find me a 4-star hotel near the Louvre for the same dates."

    The agent calls `hotel_search` and shows you matching hotels and rates. Pick one. The agent now has a hotel `htl_*` offer ID ready for the next step.
  </Tab>
</Tabs>

## 3) Build the trip with both items

Now the key step. Add the flight to a new trip and set travelers in the same call. Then add the hotel to the same trip with a second `trip(add_item)` call. Both items now live on the same trip and will check out together.

<Tabs>
  <Tab title="SDK">
    ```typescript theme={null}
    // Create the trip with the flight + travelers in one call.
    const trip = await client.trip({
      add_item: { trip_item_token: flightToken },
      upsert_travelers: {
        travelers: [{
          first_name: 'Jane',
          last_name: 'Doe',
          date_of_birth: '1990-01-15',
          gender: 'FEMALE',
          passenger_type: 'ADULT',
        }],
        contact: {
          email: 'jane@example.com',
          phone: '+33612345678',
        },
      },
    })
    const tripId = trip.trip_id

    // Append the hotel to the same trip.
    await client.trip({
      trip_id: tripId,
      add_item: { trip_item_token: hotelToken },
    })
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    # Create the trip with the flight + travelers in one call.
    TRIP_ID=$(jinko trip \
      --trip-item-token "$FLIGHT_TOKEN" \
      --travelers '[{"first_name":"Jane","last_name":"Doe","date_of_birth":"1990-01-15","gender":"FEMALE","passenger_type":"ADULT"}]' \
      --contact '{"email":"jane@example.com","phone":"+33612345678"}' \
      --format json | jq -r '.trip_id')

    # Append the hotel to the same trip.
    jinko trip --trip-id "$TRIP_ID" --trip-item-token "$HOTEL_TOKEN"
    ```
  </Tab>

  <Tab title="MCP">
    The agent calls `trip(add_item + upsert_travelers)` with the flight token, then a second `trip(add_item)` with the hotel offer ID and the same `trip_id`. From the user's point of view, this is one continuous conversation.

    <Warning>
      Travelers belong to the trip, not to each item. Set them once with `upsert_travelers` and they apply to every flight and hotel on the trip.
    </Warning>
  </Tab>
</Tabs>

The trip response now contains two `items[]`: the flight and the hotel. The total price is the sum of both, in the same currency.

## 4) Checkout

Call `checkout` once. You get one Stripe `checkout_url` covering both items.

<Tabs>
  <Tab title="SDK">
    ```typescript theme={null}
    const { checkout_url } = await client.checkout(tripId)
    console.log('Open in browser:', checkout_url)
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    jinko checkout --trip-id "$TRIP_ID"
    # → { "checkout_url": "https://app.gojinko.com/checkout?sid=...", ... }
    ```
  </Tab>

  <Tab title="MCP">
    The agent calls `checkout` and opens the checkout in a browser window. The Stripe page shows both items, the combined total, and a single payment.
  </Tab>
</Tabs>

## 5) User pays

The user opens `checkout_url`, sees the flight and the hotel side by side with one total, enters payment once, and Stripe holds the authorization.

## 6) Fulfillment

Stripe webhooks trigger fulfillment for both items. They book in parallel and the trip is `completed` once both providers confirm.

| State        | Meaning                                                |
| ------------ | ------------------------------------------------------ |
| `pending`    | Payment cleared, neither item booked yet               |
| `fulfilling` | At least one item is still being booked                |
| `completed`  | Both items confirmed, `booking_ref` populated for each |
| `failed`     | A provider rejected. Refund covers the full amount.    |

If only one of the two providers fails, Jinko rolls back the other booking and refunds the full charge. You get a single, atomic outcome: both succeed or neither does.

## 7) Watch the booking land

Poll `get_trip` until `fulfillment.status` is terminal. The `bookings[]` array will contain entries for both the flight and the hotel.

<Tabs>
  <Tab title="SDK">
    ```typescript theme={null}
    const trip = await client.getTrip(tripId)
    if (trip.fulfillment?.status === 'completed') {
      for (const booking of trip.bookings) {
        console.log(booking.type, booking.booking_ref)
      }
    }
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    jinko trip-status --trip-id "$TRIP_ID" --format json | jq '.bookings'
    ```
  </Tab>

  <Tab title="MCP">
    The agent calls `get_trip` and reports both confirmation references back to the user. Jinko also sends a single confirmation email covering the full trip.
  </Tab>
</Tabs>

## What's next?

* **Just a flight**: see the [Flight booking guide](/guides/flight-booking) for the full eight-step flow.
* **Just a hotel**: see the [Hotel booking guide](/guides/hotel-booking).
* **Add ancillaries to the flight**: see [step 4 of the flight guide](/guides/flight-booking#4-quote-and-select-ancillaries-optional).
* **Refund or exchange the flight after booking**: [flight\_refund](/tools/flight-refund) and [flight\_exchange](/tools/flight-exchange).
* **Troubleshooting**: [Errors](/concepts/errors) has the full status-code reference.
