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

# Price Drop Protection

> Add free Price Drop Protection to a booked hotel stay: when the same room costs less, Jinko rebooks it and refunds the difference.

After a hotel stay is booked, Price Drop Protection keeps searching the same hotel. When the same
room, with the same cancellation terms, costs more than USD 10 less than the traveler paid, Jinko
books it as a **new booking**, cancels the stay on the original booking and refunds the
difference to the traveler's original payment. Price Drop Protection is free.

## What it covers

* Hotel stays paid at booking, for one room, on a rate that can be cancelled for a full refund.
  For any other stay, `get_booking_ancillaries` says why it cannot be protected (see
  [When protection cannot be added](#when-protection-cannot-be-added)).
* Jinko searches the hotel once a day (`terms.check_interval_minutes`) until shortly before free
  cancellation of the stay ends (`terms.ends_at`), so that the original stay can still be
  cancelled for a full refund.
* A rebook needs the same hotel, room, board, dates and guests, free cancellation until at least
  the same date as the original booking, and a price lower by more than `terms.min_saving_money`
  (USD 10). For a booking in another currency, USD 10 is converted at the rate of the day of each
  check.
* A stay is rebooked at most once. The new booking cannot be protected again.
* When the original booking also holds a flight, the new booking holds only the hotel. The flight
  stays on the original booking.

## Add protection to a booking

Once the hotel booking is confirmed, list what can be added to it with its `booking_ref` and the
traveler's last name.

```javascript theme={null}
const api = async (method, path, body) => {
  const response = await fetch(`https://api.gojinko.com/v1/${path}`, {
    method,
    headers: {
      'X-API-Key': process.env.JINKO_API_KEY,
      'Content-Type': 'application/json',
    },
    body: body ? JSON.stringify(body) : undefined,
  })
  if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`)
  return response.json()
}

const catalog = await api('POST', 'get_booking_ancillaries', {
  booking_ref: 'JNK-A7B3X9',
  last_name: 'Doe',
})
```

Each booked hotel item that can be protected has one offer of `type` `price_drop_protection`:

```json theme={null}
{
  "booking_ref": "JNK-A7B3X9",
  "items": [
    {
      "booked_item_id": "itm_aaaaaaabxqxwycyaborn5f4cd6zh6",
      "domain": "hotel",
      "summary": "Hotel Example Paris, 2026-12-12 – 2026-12-14",
      "purchasable": true,
      "catalog_status": "ready",
      "catalog_expires_at": "2026-11-03T09:30:00Z",
      "purchase_deadline": "2026-12-09T10:00:00Z",
      "available_ancillaries": [
        {
          "trip_item_token": "pbh_254a7bcc4dd4fb497855daac8ea0cee5",
          "type": "price_drop_protection",
          "display_label": "Price Drop Protection",
          "description": "Until 2026-12-09 10:00 UTC, shortly before free cancellation ends, Jinko searches this hotel again once a day. If the same room, free to cancel until at least the same date, becomes more than $10.00 cheaper, Jinko books it as a new booking, cancels this one and refunds the difference.",
          "price_per_unit_money": { "value": 0, "currency": "USD", "decimal_places": 2, "display": "USD 0.00" },
          "terms": {
            "min_saving_money": { "value": 1000, "currency": "USD", "decimal_places": 2, "display": "USD 10.00" },
            "check_interval_minutes": 1440,
            "ends_at": "2026-12-09T10:00:00Z"
          }
        }
      ],
      "already_held": []
    }
  ]
}
```

`description` is one paragraph you can show the traveler as it is. With the traveler's approval,
add the offer to a trip of its own and check it out:

```javascript theme={null}
const trip = await api('POST', 'trip', {
  add_item: { trip_item_token: 'pbh_254a7bcc4dd4fb497855daac8ea0cee5' },
})

const checkout = await api('POST', 'checkout', { trip_id: trip.trip_id })
// checkout.status === 'fulfilling', checkout.payment_type === 'none'
```

* Send `add_item` **without** a `trip_id`: the protection is a trip of its own. The trip takes the
  travelers and contact of the booking; you do not add them.
* A trip that holds only free products has nothing to pay. Checkout answers `status`
  `fulfilling` and `payment_type` `none`, with an empty `checkout_url` and no `expires_at`. There
  is no payment step.
* The offer's `trip_item_token` is valid until `catalog_expires_at`. Protection can be added until
  `purchase_deadline`.

Through MCP, the same steps are `get_booking_ancillaries({ booking_ref, last_name })`, then
`trip({ add_item: { trip_item_token } })` without a `trip_id`, then `checkout({ trip_id })`.

## Follow the protection

`get_booking` shows `price_drop_protection` on the protected hotel item:

```json theme={null}
{
  "booked_item_id": "itm_aaaaaaabxqxwycyaborn5f4cd6zh6",
  "domain": "hotel",
  "status": "completed",
  "price_drop_protection": {
    "status": "active",
    "last_checked_at": "2026-11-03T09:00:00Z",
    "next_check_at": "2026-11-04T09:00:00Z"
  }
}
```

| `status` | Meaning |
| - | - |
| `pending` | The protection was added and is being set up. Read again shortly. |
| `active` | Jinko searches the hotel at `next_check_at`. |
| `rebooked` | The same room was booked for less. `rebook` names the new booking and the refund. |
| `ended` | No further search runs. `ended_reason` says why. |
| `unknown` | The state cannot be read right now. Read again later. |

| `ended_reason` | Meaning |
| - | - |
| `refund_window_closed` | The stay can no longer be cancelled for a full refund, so no rebook can be made. |
| `item_cancelled` | The stay was cancelled. |
| `item_changed` | The booked item changed. |

A search that finds no cheaper equivalent room changes nothing: the status stays `active` until
the next search.

## When the stay is rebooked

Jinko first books the same room at the lower price as a new booking, then cancels the stay on the
original booking. If the original stay cannot be cancelled for a full refund at that moment, Jinko
cancels the new booking instead, keeps the original, and the protection ends with
`ended_reason` `refund_window_closed`.

After a rebook:

* The original hotel item is `cancelled` and shows `price_drop_protection.status` `rebooked`,
  with the new booking in `rebook`. When the original booking held only this stay, its `status`
  is `cancelled` too; a flight on the same booking stays booked, so do not stop tracking it:

  ```json theme={null}
  "price_drop_protection": {
    "status": "rebooked",
    "last_checked_at": "2026-11-03T09:00:00Z",
    "rebook": {
      "new_booking_ref": "JNK-B7Q2M1",
      "new_booked_item_id": "itm_aaaaaaabxqxw4ya3l4z3zgnq5l3kn",
      "refund_money": { "value": 35648, "currency": "USD", "decimal_places": 2, "display": "USD 356.48" },
      "rebooked_at": "2026-11-03T09:01:10Z"
    }
  }
  ```

* The new booking holds the stay. Read it with `get_booking` on `rebook.new_booking_ref` and the
  same last name. Cancel it as any hotel booking; a refund goes to the original payment.

* `refund_money` is what the traveler paid for the original stay minus the price of the new
  booking. It is refunded to the original payment. Nothing new is charged: the original payment
  covers the new booking.

* The traveler receives one email, `hotel-price-drop-rebooked`, with the new booking and the
  cancellation of the original stay. Jinko sends no separate booking confirmation or cancellation
  email for a rebook.

* Jinko sends the `servicing.price_drop_rebooked` webhook event. See
  [Webhooks](https://docs.dev.gojinko.com/guides/webhooks.md#price-drop-protection-rebooks).

## When protection cannot be added

An item with `purchasable` `false` lists no offer. `not_purchasable_reason` says why:

| `not_purchasable_reason` | Meaning |
| - | - |
| `not_confirmed` | The stay is not booked yet. Read the catalog again once the booking is confirmed. |
| `supplier_not_supported` | Protection is not available for this hotel booking. |
| `not_pay_now` | The stay is paid at the property. |
| `not_refundable` | The rate cannot be cancelled for a full refund. |
| `multi_room` | The stay holds more than one room. |
| `refund_window_closed` | Free cancellation ends too soon for a search to run. |
| `created_by_price_drop` | This booking is itself a rebook. A stay is rebooked at most once. |
| `already_protected` | The stay is already protected. |
| `temporarily_unavailable` | The catalog cannot be read right now. Call again later. |

## Test on sandbox

On sandbox, a rebook happens only when the hotel's sandbox price is lower when Jinko searches
it. No call triggers a rebook. Test adding protection and reading `price_drop_protection` there,
and handle the `servicing.price_drop_rebooked` event from the example in
[Webhooks](https://docs.dev.gojinko.com/guides/webhooks.md#price-drop-protection-rebooks).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.