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

# Car rental booking

> Search, book, and cancel a rental car, with side-by-side examples for SDK, CLI, and MCP.

This guide walks you through renting a car from start to finish. Car rental follows the same trip flow as flights and hotels: every offer returned by `car_search` is already priced and bookable, and its `offer_id` (the `car_*` token) goes straight into a trip. There is no car-specific booking call.

The one-line version of the flow:

```
car_search → trip(add_item + travelers) → checkout → user pays → get_trip
```

And after the booking, if the customer changes their mind:

```
get_booking → car_cancel(preview) → car_cancel(commit)
```

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

A search needs five things: where and when the car is collected, when it is returned, the driver's age, and the driver's country of residence. The last two change the price and which suppliers will rent at all, so collect them from the user rather than guessing.

<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.carSearch({
      pick_up: { airport_code: 'LYS', date_time: '2026-11-12T10:00:00' },
      drop_off_date_time: '2026-11-15T10:00:00',
      driver_age: 35,
      residence_country: 'FR',
      currency: 'EUR',
    })

    const offer = result.offers[0]
    console.log(offer.vehicle.name, offer.package.supplier_name, offer.price.pay_now.display, offer.offer_id)
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    jinko car-search \
      --pick-up-airport LYS \
      --pick-up-date-time 2026-11-12T10:00:00 \
      --drop-off-date-time 2026-11-15T10:00:00 \
      --driver-age 35 \
      --residence-country FR \
      --currency EUR \
      --format json \
      | jq '.offers[0]'
    ```

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

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

    > "I need a rental car at Lyon airport from November 12 to 15. I'm 35 and live in France."

    The agent calls `car_search` and shows you offers. Pick a vehicle and a rental company.
  </Tab>
</Tabs>

A few things to know about the search:

* **Name the place the way a traveler would.** Send exactly one of `airport_code` (an IATA code, the simplest path), `place` (free text such as "Lyon Part-Dieu"), or `geo` (coordinates plus a radius, for "near me"). When free text matches several different rental locations, the response carries a `candidates` list instead of offers. Put those to the user and search again with the chosen name. Never auto-pick one: "Lyon" is an airport, a rail station, and a downtown office that book three different counters.
* **Date-times are branch-local and carry no timezone.** Send `2026-11-12T10:00:00`, never a `Z` or an offset. The rental desk works on its own wall clock, and each branch in the response carries its IANA `time_zone`.
* **Omit `drop_off` for a round trip** and set `drop_off_date_time` instead. For a one-way rental, send `drop_off` with its own place and `date_time`.
* **Offers expire.** Each offer carries `expires_at`, about 30 minutes out. After that, search again.

### Reading an offer

Every offer flattens one vehicle, one rate package, and one branch pair into a single bookable line:

| Field                                                         | What it is                                                                                                                                                                                             |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `offer_id`                                                    | The `car_*` token. Pass it verbatim to `trip(add_item)` as `trip_item_token`.                                                                                                                          |
| `vehicle.name`                                                | The car model, "or similar" unless `model_guaranteed` is true.                                                                                                                                         |
| `package.supplier_name`                                       | The rental company the driver actually collects the car from (Avis, Hertz, Europcar, Sixt). Show it with every offer: two otherwise identical offers are often different companies at different desks. |
| `package.name`                                                | The rate plan, for example "Fully Inclusive".                                                                                                                                                          |
| `price.pay_now`                                               | **The only amount Jinko charges.**                                                                                                                                                                     |
| `price.due_at_desk`, `price.deposit`, `price.estimated_total` | Collected or held by the rental desk. Display only, never sum them into a total.                                                                                                                       |
| `cancellation_fees`                                           | Fee tiers by time before pick-up. **An empty list means the supplier published no schedule.** That is unknown, never "free cancellation".                                                              |
| `on_request`                                                  | The supplier confirms availability after booking rather than instantly, so the booking settles asynchronously.                                                                                         |
| `pick_up` / `drop_off`                                        | The branches, with address, `time_zone`, `opening_hours`, and `requires_flight_number`.                                                                                                                |

Amounts are `{ value, currency, decimal_places, display }` in minor units. Show `display`; compute with `value / 10 ** decimal_places`.

<Warning>
  **A branch with `requires_flight_number: "always"` cannot be booked.** Jinko carries no flight number today, so the supplier would refuse the rental. Prefer an offer from another branch.
</Warning>

## 2) Build the trip

Add the chosen offer to a trip and set travelers in one call. The `car_*` token goes into `trip(add_item)` exactly the same way a flight `trip_item_token` or a hotel `htl_*` token does. The first traveler is the driver.

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

  <Tab title="CLI">
    ```bash theme={null}
    jinko trip \
      --trip-item-token "$CAR_OFFER_ID" \
      --travelers '[{"first_name":"Jane","last_name":"Doe","date_of_birth":"1990-01-15","gender":"FEMALE","passenger_type":"ADULT"}]' \
      --contact '{"title":"ms","email":"jane@example.com","phone":"+33612345678"}' \
      --format json | jq '.trip_id'
    ```
  </Tab>

  <Tab title="MCP">
    The agent collects the driver's details (name, date of birth, contact) from you and calls `trip(add_item + upsert_travelers)`.

    <Warning>
      The rental desk checks the driver's licence and ID at pick-up. Never let an agent fabricate traveler data. Use the driver's legal name as it appears on their licence.
    </Warning>
  </Tab>
</Tabs>

<Warning>
  **A car rental needs `contact.title`.** The honorific of the booking contact (`mr`, `ms`, `mrs`) is required by the rental supplier, and the checkout readiness gate refuses a trip with a car item and no title before any card is charged. It is optional for flight-only and hotel-only trips.
</Warning>

The driver's age and country of residence are not asked again here. They were baked into the rate at search time, which is why `car_search` insists on the real values.

## 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?t=...", ... }
    ```
  </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. Open the exact string the API returned; never build one yourself. The response also carries `expires_at`, the deadline on the quoted price, which is a different clock from the checkout link itself. See the [flight guide](/guides/flight-booking) for the full envelope and [Errors, after a quote expires](/concepts/errors#after-a-quote-expires) for what happens past it.

The customer is charged `price.pay_now` and nothing else. Anything the offer listed as due at the desk, a deposit, or an estimated total is settled between the driver and the rental company at pick-up.

## 4) User pays

Send the user to `checkout_url`. They:

1. Confirm the vehicle, dates, and branches.
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. The fulfillment states are the same as for [hotels](/guides/hotel-booking#5-fulfillment-is-automatic): `awaiting_payment → preparing → prepared → processing → confirming`, ending at one of the terminal states (`completed`, `partial`, `failed`, `cancelled`, `expired_quote`, `exchange_partial_failure`).

<Note>
  **Offers with `on_request: true` settle asynchronously.** The supplier confirms availability by hand after the booking is placed, so the trip can sit in `confirming` for a while rather than seconds. Poll until a terminal state and tell the customer the rental is pending confirmation, not confirmed.
</Note>

## 6) Watch the booking land

Poll `get_trip` until `fulfillment.status` is terminal. The loop is identical to the [hotel guide](/guides/hotel-booking#6-watch-the-booking-land): break on every terminal state, not just `completed` and `failed`.

The confirmation lives in `bookings[]` with the Jinko `booking_reference` (`JNK-XXXXXX`). The customer also receives a confirmation email from Jinko carrying the rental company's own reference, the pick-up branch, and the driver instructions.

## 7) Cancel a rental

Changing a rental (dates, vehicle, extras) is not offered. The only path to a different rental is to cancel this one and book again with `car_search`. Cancellation is a two-step flow so the customer sees the fee before anything happens.

<Steps>
  <Step title="Look up the booking">
    Call `get_booking` with the booking reference and the driver's last name, and pick the car item's stable `item_id` from `items[]`. The item carries its servicing eligibility and any refusal reason.
  </Step>

  <Step title="Preview the fee">
    Quote the fee in force and the refund it would leave. Nothing is cancelled yet. The response carries a `cancellation_id` (`ccl_...`) that binds the later commit to exactly this quote, and an `expires_at` after which you preview again.

    <Tabs>
      <Tab title="SDK">
        ```typescript theme={null}
        const preview = await client.carCancelPreview({
          booking_ref: 'JNK-A7B3X9',
          last_name: 'Doe',
          item_id: 872,
        })
        console.log(preview.fee_known, preview.fee?.display, preview.refund_amount?.display, preview.cancellation_id)
        ```
      </Tab>

      <Tab title="CLI">
        ```bash theme={null}
        jinko car-cancel preview --booking-ref JNK-A7B3X9 --last-name Doe --item-id 872
        ```
      </Tab>

      <Tab title="MCP">
        The agent calls `car_cancel` with `action: "preview"` and shows the customer the fee and the refund.
      </Tab>
    </Tabs>
  </Step>

  <Step title="Get the customer's confirmation">
    Show the fee and the refund and get an explicit yes. This ends the rental.
  </Step>

  <Step title="Commit">
    <Tabs>
      <Tab title="SDK">
        ```typescript theme={null}
        const result = await client.carCancelCommit({
          booking_ref: 'JNK-A7B3X9',
          last_name: 'Doe',
          item_id: 872,
          cancellation_id: preview.cancellation_id,
        })
        console.log(result.state, result.refund_amount?.display)
        ```
      </Tab>

      <Tab title="CLI">
        ```bash theme={null}
        jinko car-cancel commit --booking-ref JNK-A7B3X9 --last-name Doe --item-id 872 \
          --cancellation-id ccl_...
        ```
      </Tab>

      <Tab title="MCP">
        The agent calls `car_cancel` with `action: "commit"` and the `cancellation_id` from the preview.
      </Tab>
    </Tabs>

    Commit is safe to repeat: a cancellation that already went through is reported as it stands rather than cancelled twice. `car_cancel(status)` re-reads the latest attempt at any time and can never start a cancellation.
  </Step>
</Steps>

Reading the money on a preview or a commit:

* **`fee_known: false` means the supplier published no fee schedule we could resolve.** The fee and refund fields are then absent, not zero. Say "we will confirm the cancellation fee". Never present it as free cancellation.
* **`refund_pending_review: true` means the booking is cancelled but a person still owes the customer the refund answer.** Do not quote `refund_amount` as final.
* **`manual_required: true` on a preview means it cannot be completed online**, for example because the fee could not be established or the rental was changed after it was paid for. `refund_review_reason` says why. If the customer explicitly agrees to hand it to a Jinko agent, commit with `manual_ok: true`; the cancellation is then recorded as `pending` and an agent completes it and settles the refund by hand. Never send `manual_ok` pre-emptively: it is the customer's consent, not a retry flag.

A commit can be refused without anything happening, and the answer says why: the quote expired (preview again), the refund moved since the preview (a fresh `cancellation_id` is returned, show the new figure and commit that one), another cancellation is in flight, or the rental is no longer cancellable. `state: "rejected"` means the supplier declined and the booking still stands; surface `rejected_reason` to the customer.

## What's next?

* **Add a flight or a hotel to the same trip**: a car sits beside flights and hotels in one trip with one checkout. See the [Flight + Hotel guide](/guides/flight-hotel-booking) for the multi-item mechanics.
* **Every search filter and response field**: the [car\_search tool reference](/tools/car-search), the [`POST /v1/car_search` endpoint](/api/car-search), and the [`jinko car-search` command](/cli/car-search).
* **Cancellation reference**: [car\_cancel](/tools/car-cancel), the [`car_cancel_preview`](/api/car-cancel-preview), [`car_cancel_commit`](/api/car-cancel-commit) and [`car_cancel_status`](/api/car-cancel-status) endpoints, and [`jinko car-cancel`](/cli/car-cancel/preview).
* **Look up 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.
