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

# Hotel booking

> Discover, build, and book a real hotel stay, with side-by-side examples for SDK, CLI, and MCP.

This guide walks you through booking a hotel from start to finish. Hotels skip the separate price-check step you see in flight booking: each rate returned by `hotel_search` is already a live offer with an `htl_*` token you can drop straight into a trip.

The one-line version of the flow:

```
hotel_search → trip(add_item + travelers) → 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) Search hotels

Search live inventory for the destination, dates, and occupancy. Hotel rates returned here are already priced and bookable.

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

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

    const result = await client.hotelSearch({
      destination: { query: 'Paris' },
      checkin: '2026-07-15',
      checkout: '2026-07-18',
      adults: 2,
    })

    const hotel = result.hotels[0]
    const rate = hotel.rooms[0].rates[0]
    console.log(hotel.name, rate.offer_id, rate.total_amount)
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    jinko hotel-search \
      --destination '{"query":"Paris"}' \
      --checkin 2026-07-15 --checkout 2026-07-18 \
      --adults 2 \
      --format json \
      | jq '.hotels[0].rooms[0].rates[0]'
    ```

    Copy the `offer_id` (starts with `htl_`).
  </Tab>

  <Tab title="MCP">
    Ask the agent:

    > "Find a 4-star hotel in Paris from July 15 to July 18 for 2 adults."

    The agent calls `hotel_search` and shows you results. Pick a hotel and a rate.
  </Tab>
</Tabs>

The `destination` field accepts five shapes (free-text query, city plus country code, lat/lng with optional radius, place ID, or a list of hotel IDs). Use whichever matches what you have.

## 2) Build the trip

Add the chosen rate to a trip and set travelers in one call. The hotel `offer_id` (the `htl_*` token) goes into `trip(add_item)` exactly the same way a flight `trip_item_token` does.

<Tabs>
  <Tab title="SDK">
    ```typescript theme={null}
    const trip = await client.trip({
      add_item: { trip_item_token: rate.offer_id },
      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
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    jinko trip \
      --trip-item-token "$HOTEL_OFFER_ID" \
      --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 '.trip_id'
    ```
  </Tab>

  <Tab title="MCP">
    The agent collects traveler info (names, dates of birth, contact) from you and calls `trip(add_item + upsert_travelers)`.

    <Warning>
      Hotels enforce name matching at check-in. Never let an agent fabricate traveler data. Use the actual guest's legal name as it appears on a government ID.
    </Warning>
  </Tab>
</Tabs>

## 3) Checkout

Create the Stripe checkout session:

<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 automatically opens the checkout in a browser window (`openLink` via MCP Apps).
  </Tab>
</Tabs>

The `checkout_url` points at `app.gojinko.com/checkout`, a Stripe-hosted page Jinko owns.

## 4) User pays

Send the user to `checkout_url`. They:

1. Confirm the hotel, dates, and room.
2. Enter payment.
3. Stripe holds the authorization.

## 5) Fulfillment is automatic

Once the user pays, Stripe webhooks trigger fulfillment on the API. No client-side confirm step is needed.

Fulfillment states (`get_trip → fulfillment.status`):

| State        | Meaning                                                   |
| ------------ | --------------------------------------------------------- |
| `pending`    | Payment cleared, booking not yet attempted                |
| `fulfilling` | Calling the hotel provider (typically seconds)            |
| `completed`  | Reservation confirmed, `booking_ref` populated            |
| `failed`     | Provider rejected (rare). Refund is issued automatically. |

## 6) Watch the booking land

Poll `get_trip` until `fulfillment.status` is terminal:

<Tabs>
  <Tab title="SDK">
    ```typescript theme={null}
    async function waitForBooking(client, tripId: string) {
      while (true) {
        const trip = await client.getTrip(tripId)
        if (trip.fulfillment?.status === 'completed') {
          console.log('Booked! Ref:', trip.bookings[0].booking_ref)
          return trip
        }
        if (trip.fulfillment?.status === 'failed') {
          throw new Error('Booking failed, refund in progress')
        }
        await new Promise(r => setTimeout(r, 5000))
      }
    }
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    while true; do
      status=$(jinko trip-status --trip-id "$TRIP_ID" --format json | jq -r '.fulfillment.status')
      echo "Status: $status"
      [[ "$status" == "completed" || "$status" == "failed" ]] && break
      sleep 5
    done
    ```
  </Tab>

  <Tab title="MCP">
    The agent can poll with `get_trip`, or the user receives a confirmation email directly from Jinko when the booking lands.
  </Tab>
</Tabs>

## What's next?

* **Add a flight to the trip**: see the [Flight + Hotel guide](/guides/flight-hotel-booking) for one trip with two items and one Stripe checkout.
* **Flight-only booking**: see the [Flight booking guide](/guides/flight-booking).
* **Search by location, chain, or amenities**: the [hotel\_search tool reference](/tools/hotel-search) covers every filter (star rating, hotel type, chain, facilities, geo radius).
* **Lookup a booking after the fact**: [get\_booking](/tools/get-booking) finds a booking by reference and last name without needing a login.
* **Troubleshooting**: [Errors](/concepts/errors) has the full status-code reference.
