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

# Changelog

> Every version of the public REST contract since 0.57.0, newest first: what was added, changed, deprecated or removed.

The contract version is `info.version` in the [OpenAPI document](/api-reference/introduction), and each section below is one version of it. The rules for what counts as breaking and how much notice a removal gets are on [Versioning & deprecation](/concepts/versioning).

This page starts at 0.57.0 (2026-10-13). Earlier versions are not listed one by one; [Contract changes](/concepts/versioning#contract-changes) on Versioning & deprecation describes what changed in 0.26.0, 0.33.0 and 0.39.0.

## 0.86.0

Not yet released.

**Changed**

Corrections to the `POST /v1/flight_search` document only; nothing on the wire moves (the second step of the live flight search review, JIN-2837).

* `via_airports` restricts every connection of a leg to the listed airports, not "at least one". Some providers are asked for at most the first 9 per leg; the whole list is applied to what they return, and the 200 description no longer claims a 9-airport cap.
* The filters `refundable_only`, `changeable_only` and `checked_bag_included` are no longer described as the same predicate as the fare flags `is_refundable`, `is_changeable` and `checked_bag_included`. The filters read the before-departure rules only, and the bag filter needs a free bag on every segment; the flags are folded from every rule (an after-departure rule alone makes them `true`) and the bag flag reads the first segment that states an allowance.
* `max_total`: conversion to `currency` before the comparison is the usual case, not the rule. A provider whose first priced offer is already in `currency` drops its other-currency offers as unverifiable.
* `offers[].offer_id` is `offer__` followed by 8 hex characters (the example was `off_…`). It is the `offer_token` of a re-price and the prefix of each `trip_item_token`, which is now described with a real-shaped example.
* `offers[].origin.code` and `destination.code` are the city codes of the requested pair, never the airport flown, and can be an empty string when the platform cannot map the code to a city. The examples are city codes and names.
* On a live segment, `flight_number` is a bare number (the cached segment keeps the either-form text), `departure_city` and `arrival_city` are IATA city codes (the examples were names), and `operating_carrier` is no longer promised on every segment: whether equality with `airline` is stated depends on the provider.
* `offers[].fares[].expires_at`: the platform's token lifetime is 75 minutes from `searched_at`, not 30.
* `included_baggage` and `carry_on_baggage` are read from the first segment that states an allowance, not per leg. Their `description` is text the platform writes from the allowance (`1 bag, 23KG each`, `No checked bags included`, `1 carry-on bag`), not the carrier's prose; `weight_unit` is `KG` or `LB`, absent when no recognised unit was stated.
* `refund_policy` and `change_policy` list all eight values each: the four base terms and their ` after departure` variants. A cancellation rule with an unspecified penalty reads `Non-refundable`; a change rule with one sets no policy.
* Fields that had no description now have one: `departure_date`; `offer_token` (a cached-itinerary token is re-shopped live and `exact_match_found` says whether the same flights and cabin were found; an `offer_id` from an earlier `flight_search` is answered from the cache of that search unless the passengers or `currency` differ, and answers `410 OFFER_EXPIRED` once its fares have expired); `multi_fare` (at most 10 fares per offer); `connection_time_min_minutes`, `connection_time_max_minutes` and `aircraft_types` (an itinerary the filter cannot verify is dropped); `same_origin_airport_only` and `same_turnaround_airport_only` (round trip only, no effect on a one-way search); `limit` (omitted means the cheapest flight plus up to 100 alternatives); the segment's `aircraft` (absent when the provider reports no equipment); and the fare's `brand_name`.

## 0.81.0

Not yet released.

**Removed**

* `aircraft` on the segments of a cached itinerary, on `POST /v1/flight_calendar`, `/v1/find_dates`, `/v1/lowest_fare` and `/v1/find_destination`. The cache carries no equipment, so the field was never sent there; it leaves the document and no response changes. The cached itinerary's legs are now their own components, `CachedFlightLeg` and `CachedSegment`, with the same fields and types as `FlightLeg` and `Segment`, which `flight_search` and the booking reads keep unchanged, `aircraft` included.

**Changed**

Corrections to the document only; nothing on the wire moves.

* On a cached leg or segment, a datetime at an airport whose timezone the platform does not know is written as UTC (`…Z`) with its local wall-clock digits. The shared text said the offset was omitted, which is what a live leg does. The live text now scopes its `*_local` remark to booked and exchanged itineraries.
* `operating_carrier` on a cached segment is sent only when the cached record carries one. The shared text promised it was always sent, equality with `airline` included.
* `limit` is described per route: on `flight_calendar` a global cap on the sorted itineraries; on `find_dates` the number of date pairs, each with its cheapest itinerary, default 20, where the route summary promised ten; on `lowest_fare` not applied, the route returns at most ten itineraries; on `find_destination` the number of destinations.
* On `POST /v1/price_monitoring`, `status` is the enum `ok` | `stale` rather than a free string; the four `*_local` datetimes carry the airport's UTC offset (RFC 3339) despite their suffix, as they always did on the wire, and their examples now show it; `inbound_stops` is documented as omitted on a one-way monitor and on a non-stop return, unlike `outbound_stops`, which is always sent.

## 0.80.0

Not yet released.

**Deprecated**

* `amount` on `price_per_person` of a `POST /v1/flight_search` fare leaves the OpenAPI document. The per-person fare is now published as `MoneyWithoutAmount`: the `Money` shape without its deprecated `amount` member — `value` (an integer in minor units), `currency`, `decimal_places` and `display`, unchanged. The platform never set `amount` on this figure, so no response changes; the member stays in the code for now and is listed under [Deprecated fields](/concepts/versioning#deprecated-fields). `total_price` on the same fare is unchanged.

## 0.79.0

Not yet released.

**Deprecated**

* The passenger fields on the cache-backed flight requests leave the OpenAPI document: `adults`, `children`, `infants_in_lap`, `infants_in_seat`, `youth`, `student`, `ages` and `residency_country` on `POST /v1/flight_calendar`, `/v1/find_dates`, `/v1/lowest_fare` and `/v1/find_destination`, and `adults`, `children` and `infants` on `POST /v1/price_monitoring`. Every cached fare is one adult's whatever party is sent (`priced_for: one_adult`), so none of them ever changed an answer. A request that still sends one is accepted, validated and answered exactly as before, with `Deprecation: true` and the deprecated-inputs `Link`; no removal date is set yet. They are listed under [Deprecated inputs](/concepts/versioning#deprecated-inputs). Price a party with `POST /v1/flight_search`, which is unchanged.
* On those requests a `passengers` object is still refused with 422, and the message now says the request takes no passenger counts instead of naming the fields above.

## 0.78.0

Released 2026-10-19.

**Added**

* Seat maps: `GET /v1/trip/{trip_id}` returns `seat_map_url`, a link to a page where the traveler picks seats on a seat map. The link expires; read the trip again for a fresh one. See [Seats](/concepts/ancillaries/seats).
* Seat offers in `GET /v1/trip/{trip_id}/ancillaries` carry `seat_map_status`: `pending` while the seats are still loading, `ready` once they can be selected.
* Selected and booked seats can carry `not_charged: true`: this seat is not charged to the traveler.

## 0.77.0

Released 2026-10-19.

**Restored**

Inputs and a response field removed without notice since 2026-08-29 that the platform still serves come back, deprecated, until 2027-04-30. A request that uses one of the restored inputs is answered as it was before the input was removed, with `Deprecation: true`, the deprecated-inputs `Link` and `Sunset: Fri, 30 Apr 2027 23:59:59 GMT`; each is listed under [Deprecated inputs](/concepts/versioning#deprecated-inputs). The restored response field sends no header, since reading a field is invisible to the server: it is marked `deprecated` in the OpenAPI document and listed under [Deprecated fields](/concepts/versioning#deprecated-fields).

* The two `enable_*` query parameters on `hotel_search` that turned individual hotel suppliers on or off, refused since 0.57.0, are accepted and ignored: the platform chooses which hotel inventory to search.
* `date_flexibility_days` on `POST /v1/flight_search`, refused since 2026-08-31, is accepted within its published range (an integer from 0 to 3) and ignored: the search runs on the exact dates, as it did then.
* On `POST /v1/exchange_shop`, a `segments_to_exchange` entry may again name its airports as flat `origin` / `destination` codes, read as airports, and an empty `segments_to_exchange` reshops like leaving the field out, as before 0.43.0.
* `hotel_ref` is back on `hotel_search` hotels and nearby alternatives, `hotel_details.hotel` and `hotel_reviews.hotel`, marked `deprecated` and sent until 2027-04-30 (listed under [Deprecated fields](/concepts/versioning#deprecated-fields)). `hotel_id` (search) and `id` (details, reviews) stay the identifier to use.

**Unchanged**

* `ticket_numbers` on the refund and exchange routes, `place_id` on `hotel_search` and a child age outside 0 to 17 stay refused.

## 0.76.1

Released 2026-10-18.

**Changed**

* `POST /v1/book` answers until 2027-04-30 instead of 2026-12-31; its `Sunset` header says so. The earlier date, published on 2026-09-07, gave less than the 180 days of notice this policy promises. `/v1/book` stays deprecated in favour of `POST /v1/checkout`.

## 0.76.0

Released 2026-10-18.

**Added**

* A request that uses a deprecated input is answered with `Deprecation: true` and `Link: <https://docs.gojinko.com/concepts/versioning#deprecated-inputs>; rel="deprecation"`, and with `Sunset` when the input has a removal date. The response is otherwise unchanged; a request refused for naming one value under both its old and new name carries the headers too. The deprecated inputs are listed on [Versioning & deprecation](/concepts/versioning#deprecated-inputs).
* `refund_check`, `refund_commit`, `refund_status` and the one-step `hotel_cancel`, deprecated in 0.74.0, answer with `Deprecation: true` and a `Link` to the route that replaces each (`rel="successor-version"`). No removal date is set, so they send no `Sunset`.

**Restored**

* The booking handles 0.57.0 removed without notice are accepted again, deprecated, until 2027-04-30: `order_id` instead of `booking_ref` on `refund_check`, `refund_commit`, `refund_status` and the exchange routes; `provider_reference` instead of `booking_ref` and `last_name` on `flight_refund_preview`, `flight_refund_commit`, `flight_refund_status`, `hotel_cancel_preview`, `hotel_cancel_commit` and `hotel_cancel_status`; `provider_booking_id` the same way on `hotel_cancel`; and `provider` on the flight refund and exchange routes and on `hotel_cancel`, which the platform does not need. A handle sent beside `booking_ref` is refused with 422. `booking_ref` is optional on these routes again, and required when no handle names the booking. A request that sends a handle gets `Sunset: Fri, 30 Apr 2027 23:59:59 GMT`.

## 0.75.0

Released 2026-10-18.

**Added**

* `booking.completed` data lists `items[]`: each booked item with its `item_id`, `kind`, `booked_item_id` and `supplier_references`, as `booking.partial` lists its completed items. You no longer need a `get_booking` call after every booking to learn the airline or hotel reference. `booked_item_id` is the `itm_` handle `get_booking` names the item by, and is omitted when the platform cannot name it. Events sent before this version carry no `items`.

## 0.74.2

Released 2026-10-18.

**Fixed**

* `exchange_price` and `exchange_commit` refuse an empty `offer_id` with 422 `BAD_REQUEST`. It used to come back as 502 `UPSTREAM_ERROR`.
* `get_booking` `booked_ancillaries[].type` lists every kind that is booked: `seat`, `bag`, `meal`, `cabin_bag_upgrade`, `sports_equipment`, `priority_boarding`, `assistance`, `insurance`, `lounge`, `frequent_flyer`, `other`. The contract said seats only while bags and meals were already listed.
* `flight_refund_preview` and `hotel_cancel_preview` document the 409 `active_operation_exists` a preview answers while another operation is in progress on the item, naming it in `active_operation`.
* Payment attempt answers document that `trip_id` carries the `trip_` prefix, which the trip answers do not.

## 0.74.1

Released 2026-10-18.

**Fixed**

* `get_booking`: `booking.booking_ref` and `items[].booking_ref` are always the booking's own Jinko reference, including after an exchange and when the booking is looked up with the reference of one of its exchanges. In 0.74.0 an exchanged item named the exchange's own reference. The deprecated `booking_reference` fields keep their meaning.

## 0.74.0

Released 2026-10-17.

**Breaking**

* `error.code` is uppercase everywhere. `attempt_in_progress`, `attempt_terminal`, `credential_format_invalid`, `departure_date_in_past`, `idempotency_key_reused`, `invalid_exchange_request`, `invalid_segments_to_exchange`, `leg_already_departed`, `leg_dates_out_of_order`, `not_exchangeable`, `partial_exchange_unsupported`, `payment_credential_invalid`, `payment_type_not_enabled`, `temporarily_unavailable` and `trip_owned_by_other_payment` are now sent in uppercase (for example `ATTEMPT_IN_PROGRESS`), and `quote_expired` is now `QUOTE_EXPIRED`.
* `payment_error.code` on payment attempts and payment refusals is uppercase, with the same codes as `error.code`.
* On refund and cancellation conflicts, the sibling `code` `quote_expired` → `QUOTE_EXPIRED`. The other sibling codes are unchanged.
* Exchange and ancillary `status` values are lowercase: `confirmed`, `pending`, `partial_failure` and `failed` on the `exchange_commit` and `exchange_status` answers; `pending`, `confirmed` and `failed` on selected and booked ancillaries.
* `failure_reason` reads `offer_unavailable`, where it read `offer_not_available`, in the `booking.failed` data and wherever a booking failure is reported.
* The travel demand `trip_type` is `oneway` or `roundtrip` on `/v1/destination`, `/v1/market`, `/v1/audience`, `/v1/trend`, `/v1/destination/trend`, `/v1/market/trend` and `/v1/audience/trend`. Requests still accept `One-Way` and `Round-Trip`.

**Added**

* `booking_state` on `GET /v1/trip/{trip_id}` and on the booking `get_booking` returns: a summary of the booking's progress after payment, one of `awaiting_payment`, `processing`, `completed`, `partial`, `failed`, `cancelled` or `needs_review`. It is absent before payment. `fulfillment.status`, the booking's `status` and `booking_status` stay as the detailed status.
* `POST /v1/agent_payment/submit` declares 404 `NOT_FOUND` when the trip, or the `quote_id` among its quotes, is not found. Nothing is charged.
* The answer to the legacy flat body of `POST /v1/agent_payment/submit` names the quote it charged as `quote_id`.

**Changed**

* Renamed request fields. Only the new name is documented; the old name is still accepted. Sending both names with different values is refused with 422 `BAD_REQUEST`, or 400 `CREDENTIAL_FORMAT_INVALID` on a typed payment body.
  * The integer `item_id` → `booked_item_id` on `refund_check`, `refund_commit`, `refund_status`, `flight_refund_preview`, `flight_refund_commit`, `flight_refund_status`, `hotel_cancel`, `hotel_cancel_preview`, `hotel_cancel_commit`, `hotel_cancel_status`, `car_cancel_preview`, `car_cancel_commit`, `car_cancel_status`, `exchange_shop`, `exchange_price`, `exchange_commit` and `exchange_status`.
  * `quote` → `quote_id` on `flight_refund_commit` and `hotel_cancel_commit`; `cancellation_id` → `quote_id` on `car_cancel_commit`; `session_reference` → `quote_id` on `exchange_commit`; the deprecated `quoted_cart_id` → `quote_id` on the typed `POST /v1/agent_payment/submit` body.
  * `max_price` → `max_total` on `flight_search` and `price_monitoring`.
  * `offer_id` → `trip_item_token` on the Best Price Guarantee claim.
  * `pax_ref_id` → `traveler_ref` on `select_ancillaries` selections; `passenger_type` → `traveler_type` on `upsert_travelers`.
* The webhook `event_id` is documented as an opaque string, unique to each occurrence. Its format is not part of the contract.
* On the cached flight searches, `max_total` is documented as a cap on the cached fare's total, and cached fares are priced for one adult. `direct_only` is documented as their only stop filter.

**Deprecated**

* Renamed response fields. Each new field is sent beside the old one, which keeps the same value and is marked `deprecated: true`. No removal date is set.
  * `quoted_cart_id` → `quote_id` on the checkout answer, the trip's `quote`, the trip ancillaries answer, payment attempts, payment refusals and the quote-expired answer; `quote` → `quote_id` on the flight refund and hotel cancellation previews; `cancellation_id` → `quote_id` on the rental car cancellation answers; `session_reference` → `quote_id` on `exchange_price`. `quote_id` is a string, and the deprecated names stay beside it.
  * `requote` → `retry_quote_id` on refund and cancellation conflicts.
  * `booking_reference` → `booking_ref`, the Jinko booking reference, on the `get_booking` booking and its items, the `exchange_commit` and `exchange_status` answers and the one-step `hotel_cancel` answer; `fulfillment_cart_id` → `booking_ref` on the trip's `fulfillment`, payment attempts and the legacy agent payment answer. The deprecated `fulfillment_cart_id` is an internal number.
  * The integer `item_id` → `booked_item_id`, the `itm_` handle the servicing routes take, on `get_booking` items and on payment attempt `items[]`; the integer `item` → `booked_item_id` on servicing webhook data; `item` → `booked_item_id` on the refund and cancellation previews, where `item` already held the same `itm_` value.
  * `quoted_item_id` → `item_id` on the `select_ancillaries` answer.
  * `pnr` and `airline_locators` on flight details, `hotel_confirmation_number` on hotel details, `confirmation.confirmation_number`, `bookings[].booking_reference`, `bookings[].pnr` and the `booking.partial` item's `booking_reference` → `supplier_references[]`, one `{ issuer, reference }` per confirmation the airline, hotel or rental company itself issued, `[]` when none is known. It is on each `get_booking` item, each `bookings[]` entry of `GET /v1/trip/{trip_id}` and each `booking.partial` item.
  * The single `airline_locator` on the exchange answers and `confirmation_number` on the one-step `hotel_cancel` answer: read the booking's `supplier_references[]` from `get_booking` instead.
  * `offer_id` → `trip_item_token` on hotel, rental car and Best Price Guarantee offers. The `offer_id` echo on trip items and bookings is deprecated; hotel and rental car items now also echo `trip_item_token`.
  * `id` → `offer_token` on cached itineraries.
  * `pax_ref_id` → `traveler_ref` and `passenger_type` → `traveler_type` on stored travelers and on ancillaries. `traveler_type` is lowercase: `adult`, `child`, `infant`.
  * `reason_code` → `failure_reason` in the booking failure email content, in the words of the `booking.failed` data.
  * `stripe_checkout_session_url` → `checkout_url` on `exchange_commit`.
* `stripe_reference` and `stripe_refund_id`.
* `refund_check`, `refund_commit` and `refund_status`, in favour of `flight_refund_preview`, `flight_refund_commit` and `flight_refund_status`; the one-step `hotel_cancel`, in favour of `hotel_cancel_preview`, `hotel_cancel_commit` and `hotel_cancel_status`.
* The webhook envelope's `status`: branch on `event`, which already names the outcome.
* The trip `status` values that describe booking progress (`fulfilling`, `fulfilled`, `partially_fulfilled`, `failed`): read `booking_state`.

**Fixed**

* `POST /v1/checkout` says it waits about 55 seconds for the quote. It said 25.
* `GET /v1/trip/{trip_id}/ancillaries` declares its 202 answer (`status: "pricing"`, with `Retry-After`), which it already returned.
* `Idempotency-Key` is documented as required on a typed payment submission. A submission without it is refused with 400 `CREDENTIAL_FORMAT_INVALID`.
* `origin` is documented as required on `/v1/destination` and `/v1/destination/trend`, which refuse a request without it.
* `TRIP_STATE_CONFLICT` no longer says a trip refuses writes while it is being quoted. A trip refuses writes once payment has started, or when it is cancelled or failed. `QUOTE_PENDING` lists `quote_id` among its extra keys.

## 0.73.0

Released 2026-10-17.

**Deprecated**

* On `hotel_search`, each of these is still accepted or still sent with its behaviour unchanged. No removal date is set.
  * `adults`, `children` and `rooms` → `occupancies`, one element per room, with children given by `children_ages`. The shorthand is ignored whenever `occupancies` is sent.
  * `star_rating` → `min_star_rating` and `max_star_rating`. `star_rating` only fills a bound the request leaves unset.
  * `offset` → `next_handle`. A search that answers with `next_handle` already refuses a non-zero `offset`.
  * The `search_handle` answer field → `next_handle`. No endpoint accepts `search_handle`.

## 0.72.0

Released 2026-10-17.

**Breaking**

* `hotel_ref` is removed from `hotel_search` hotels, from `hotel` on `hotel_details` and from `hotel` on `hotel_reviews`. One identifier remains: `hotel_id` on a search hotel and `id` on details and reviews, with the same value. `/v1/hotel_details/{hotel_id}` and `/v1/hotel_reviews/{hotel_id}` take that value, and still accept a `hotel_ref` kept from an earlier search.

**Changed**

* `hotel_id` is opaque. Pass it back exactly as received to `hotel_details`, `hotel_reviews` and `hotel_ids`. Do not parse or build it, and do not keep it across contract versions: the value a hotel is known by can change, so read it from a fresh `hotel_search`.

**Fixed**

* The `hotel_details` example `id` shows the same value as the `hotel_search` example.

## 0.71.0

Released 2026-10-17.

**Added**

* `hotel_search` declares `destination_resolution.radius_source`, which destination searches already sent beside `radius_km`: `caller` when the request's own `radius_km` was searched, `city` when the radius came from the spread of the resolved city's hotels (no smaller than the platform minimum), and `default` when coordinates came without a radius and the platform default was used. A `city` or `default` radius may be widened when the first search finds too little; `radius_km` is then the radius finally searched. It is absent on the `hotel_name` and `hotel_ids` shapes.

**Removed**

* `destination_resolution.place_id`, and `place_id` as a value of `destination_resolution.shape`. No answer carried them, since a `place_id` destination is refused on input with 422. `shape` is one of `city`, `coordinates`, `query`, `hotel_name` and `hotel_ids`, and its description says which other fields each one brings.

## 0.70.0

Released 2026-10-17.

**Breaking**

* `hotel_search` no longer returns `pagination`, which some destination searches sent although the contract never declared it. Page with `next_handle`, `has_more` and `expires_at`; `total` still counts the hotels on the page.
* Each entry of `hotels[].offers[]` and `nearby_alternatives[].offers[]` no longer carries `semantic_matches`, `promotions[].code` or `room_rates[].rate_id`. No operation accepted `rate_id`. `promotions[].name` and `promotions[].remark`, which must be shown to the guest, stay, as do `room_id`, `occupancy_number`, `occupancy` and the rest of each room rate. The deprecated `rooms[].rates[]` shape, including its promotion `code`, is unchanged.

## 0.69.0

Released 2026-10-17.

**Breaking**

* `hotel_search` no longer returns `budget_filter`, the report of how `max_budget_per_night` was applied. The filter itself is unchanged: `max_budget_per_night` stays on the request, every hotel returned fits it, `empty_reason` is `all_dropped_by_budget` when none did, and `warnings` still carries the budget advisories.
* `hotel_search` no longer returns `search_context.trip_id`. `trip_id` on a first request is no longer declared; a request that still sends it is accepted and the field is ignored. It never selected, priced or filtered anything. Adding a hotel offer to a trip with `POST /v1/trip` is unchanged.

## 0.68.0

Released 2026-10-17.

**Breaking**

* `hotel_search` no longer returns `search_scope`, which described how the search ran: `plan_kind`, `coverage`, `snapshot_size`, `candidate_count`, `coverage_unknown`, `truncated`, `widened_from_km` and `providers[]`. Paging is unchanged, and `destination_resolution` still reports the radius a destination search ended at.

## 0.67.0

Released 2026-10-16.

**Breaking**

* `hotel_search` no longer returns `intent_match_probability`, `intent_confidence`, `intent_evidence_status` or `intent_facts` on hotels in `hotels` and `nearby_alternatives`, nor `internal_search` on the answer. `distance_km` and `recommendation_score` stay, and the order of `hotels` and `recommendation_score` still reflect `intent.user_intent`.

## 0.66.0

Released 2026-10-16.

**Fixed**

* The legacy flat body of `POST /v1/agent_payment/submit` no longer declares `intent`. A body that still carries it is accepted and the field is ignored. Before, such a submission was refused with `CREDENTIAL_FORMAT_INVALID`.

## 0.65.0

Released 2026-10-16.

**Breaking**

* The Best Price Guarantee answer no longer carries `negotiator`. Read `status`, `offer` and `decline`; `offer.reference.verified` still says whether Jinko confirmed the reference.

## 0.64.1

Released 2026-10-15.

**Fixed**

* The Best Price Guarantee answer's example is one complete `offered` answer. It showed every money field with the same value, beside `decline` and `poll_after_seconds`, which an `offered` answer does not carry. No field or rule changed.

## 0.64.0

Released 2026-10-15.

**Added**

* `exchange_shop` says why an exchange is not automatic. When `support_level` is not `AUTO`, the answer carries `reason_code` (`NOT_EXCHANGEABLE` with `UNSUPPORTED`, `MANUAL_REQUIRED` with `MANUAL_REQUIRED`) and `reason`, a fixed sentence to show the customer: "This booking cannot be exchanged online. Contact Jinko support." or "This request needs manual handling by Jinko support." An `AUTO` answer carries neither.
* `exchange_price` adds `customer_amounts.residual_value`, in the money shape and in the booking's charge currency: the part of the original fare the airline keeps as a credit in the traveller's name after an exchange to a cheaper fare. It is not refunded to the card, is not part of `total_due` or `total_refund`, and is present only when the airline reports one.

**Changed**

* `exchange_price` `customer_amounts.new_fare` no longer includes the residual value, so the figures add up as `original_fare + total_due - total_refund = new_fare + penalty_amount + residual_value`. Before, an `EVEN` exchange to a cheaper fare showed a new ticket worth exactly the old one.
* `refund_check` states a reason on every answer that is not `AUTO`. `manual_reason` is present with `UNSUPPORTED` as well as with `MANUAL_REQUIRED`, and an `UNSUPPORTED` answer always carries a `reason_code`: a refusal that came with no code now carries `NOT_CANCELLABLE`. A refusal's `manual_reason` reads the same sentence as the `flight_refund_preview` 409 `not_cancellable` for that booking, and that 409 carries the same `reason_code`.
* `provider.references.cancellation_reference` on the refund and cancellation status answers is documented as the reference the supplier issued for the cancellation or refund itself, for example the ticket number a void settles or the refund id. It is never the supplier's identifier for the booking, and it is absent when the supplier issued none.

## 0.63.0

Released 2026-10-15.

**Added**

* Best Price Guarantee: `POST /v1/best_price_guarantee` and `GET /v1/best_price_guarantee/{id}`. The submit takes a hotel offer from `hotel_search` and the lower price the traveller found for the same stay on another site. Both routes answer the state of the request: `pending`, `offered` with a new offer to add to a trip, or `declined` with `decline.reason` and, where Jinko compared the reference page, `decline.detail`. They take a `jnk_` API key, as `X-API-Key` or as a Bearer token. Where the feature is not enabled for your key, the answer is 200 with `status: declined` and `decline.reason: feature_disabled`.

## 0.62.0

Released 2026-10-15.

**Added**

* `hotel_search` `empty_reason` can be `all_dropped_by_rating_filter`: the destination matched properties, but every one fell below the requested `min_rating` or `min_reviews`, or below the default `min_rating` of 7.0 when the request set none. A property whose rating is unknown is removed only by a `min_rating` the request sets, and one whose review count is unknown only by `min_reviews`.

**Fixed**

* The `min_rating` description states the default of 7.0, and that `0` turns the floor off on destination searches.
* The `hotel_name` description says property filters sent with `hotel_name` or `hotel_ids` are rejected. It said they were ignored.

## 0.61.0

Released 2026-10-14.

**Added**

* Each flight fare carries `refund_policy` (`Free cancellation`, `Refundable`, `Refundable with fee` or `Non-refundable`) and `change_policy` beside `is_refundable` and `is_changeable`, stating the refund and change terms in words.

## 0.60.0

Released 2026-10-14.

**Breaking**

* Checkout of a trip with a flight requires a contact email. Without one, checkout is refused with 422 `MISSING_CUSTOMER_DETAILS` naming `email`.
* In a trip with a hotel, a `child` or `infant` traveller requires a `date_of_birth`. A traveller without one is refused on that field.
* `date_of_birth` and `passport_expiry` must be exactly `YYYY-MM-DD`.

**Changed**

* Travellers are checked against the trip before payment. In a trip with a flight, `upsert_travelers` (once the trip holds the flight) and `checkout` answer 400 `BAD_REQUEST`, with `error.field` naming the traveller, when a `date_of_birth` is in the future, when `traveler_type` does not match the traveller's age on the date the booking counts ages from (the trip's first flight, or for some bookings the return flight of a round trip: `adult` 12 or older, `child` 2 to 11, `infant` under 2 and still under 2 on the last flight), or when `passport_expiry` is before the trip's last flight. Before, such a trip reached a payable checkout or failed at checkout with 502 `UPSTREAM_ERROR`.
* Each hotel in a trip is booked for the trip's travellers, or for the room guests that `hotel_room_guests` names on a `room_bundle` hotel. They cannot include more adults or more children than its rooms were priced for, counted by age at check-in, and each child's age at check-in must be one of the priced `children_ages`. Otherwise the answer is 400 `BAD_REQUEST` naming the first traveller over a count or with an age the room was not priced for.
* A flight searched for a different party is re-priced at checkout for the trip's travellers, on the same flights and fare family. When that is not possible, or the airline declines to price the fare, checkout answers 409 `OFFER_UNAVAILABLE`.

## 0.59.2

Released 2026-10-14.

**Changed**

* A card declined on the payment page is reported on the trip. When the issuer declines the card the payer used on the `checkout_url` page, or its authentication fails, `payment_attempt.payment_error.code` is `CARD_DECLINED` or `AUTHENTICATION_FAILED` while `payment_status` stays `pending` and `recovery.action` is `poll`: the payer can use another card on the same page until the quote expires. `payment_error` returns to `null` once a card is authorized. Before, the trip read `pending` with `payment_error: null`.

**Fixed**

* On a payment attempt, `booking_ref` and the deprecated `fulfillment_cart_id` are absent until the booking and its reference exist, and `retry_after_seconds` is present only while `recovery.action` is `poll`. None of them is ever `null`. Before a booking existed, the first two were sent as `null`, which the published schema does not allow.

## 0.59.1

Released 2026-10-14.

**Added**

* `get_booking` declares the booked flight itinerary, which it already sent. Flight details carry `legs[]`, each with the origin, the destination, `departure_datetime`, `arrival_datetime`, `stops` and `segments[]`, and the older names `outbound_flight` and `inbound_flight` for `legs[0]` and `legs[1]`. Each segment carries the carriers, `flight_number`, `aircraft`, the airports, and `departure_time` and `arrival_time` (airport local time with its UTC offset) beside `departure_time_local` and `arrival_time_local` (no offset). The itinerary is the one booked and ticketed: it is not refreshed from the airline, and no webhook reports an airline schedule change or cancellation. There is no terminal and no per-segment status.
* Booking and item `status` values are documented as an open set: in progress (from `pending` to `confirming`), final (`completed`, `partial` on the booking, `failed`, `cancelled`, `expired_quote`) and needing attention (`exchange_partial_failure`, `needs_review`). They follow Jinko's booking and servicing steps, never an airline change.

**Fixed**

* The `get_booking` status example reads `completed`, which a ticketed booking returns. It said `confirmed`.

## 0.59.0

Released 2026-10-14.

**Breaking**

* `slices` → `legs` on `exchange_shop` offers, which no longer carry `metadata`. The new fare on `exchange_price` no longer carries `baggage_source`. The exchange answers, `exchange_commit` included, carry only the fields the contract declares.
* `exchange_commit` no longer returns `exchange_reference`. On some exchanges it held the supplier's reference for the booking the change creates, and no route takes it as input: `exchange_status` reads by `booking_ref` and `last_name`.

**Added**

* `exchange_shop` declares each offer's itinerary and fare: `legs[]` with `stops`, `duration_minutes` and `segments[]`, whose members use the names of a flight search segment (`airline`, `operating_carrier`, `flight_number`, `departure_airport`, `departure_time`, `arrival_airport`, `arrival_time`, `duration_minutes`), the fare per segment (`cabin_name`, `fare_brand_key`, `fare_basis_code`, `rbd`) and the fare per offer (`fare_basis_codes`, `booking_classes`, `cabin`). Exchange times are local wall-clock time with no UTC offset, unlike a flight search segment's.
* `exchange_price` declares the new fare's terms and baggage: `new_fare_details` adds `fare_brand_name`, `fare_brand_code`, `fare_basis_codes`, `endorsements`, `is_refundable`, `is_changeable`, `refund_policy`, `change_policy`, `checked_baggage` and `carry_on_baggage`, and the extra bag options declare `is_included`, `segment_id` and `journey_id`. `fare_change_notice` is declared.

## 0.58.0

Released 2026-10-14.

**Added**

* `GET /v1/hotel_reviews/{hotel_id}`: the guest score and review count, a summary of what guests praise and criticise, a score per category (cleanliness, location, staff and others) and a page of recent reviews, newest first, each with its headline, score, pros and cons, date and language. Page with `limit` (1 to 50, default 10) and `offset`. It takes the same `hotel_id` as `hotel_details`.
* `hotel_details` adds `rating`, `review_count`, `child_allowed`, `pets_allowed`, `facility_items`, `rooms[].amenity_items`, `rooms[].max_adults` and `rooms[].max_children`, and `child_allowed` and `pets_allowed` on the policy. `max_adults` and `max_children` are how many adults and children a room accepts; `max_children: 0` means the room does not take children. `facility_items` pairs each facility with its Jinko facility dictionary id, the value `facility_ids` accepts on `hotel_search`; a facility without a dictionary id stays in `facilities` only. `rooms[].amenity_items` carries room amenity ids, a separate list from the facility dictionary.
* 422 `MISSING_CUSTOMER_DETAILS` on `checkout` and `book` carries `missing_fields` and `missing_by_domain` when the platform can name what the booking still needs, such as the passport fields an airline requires: the field names as `upsert_travelers` takes them (`passport_number` and others), and the same names grouped by product (`flight`). Both are absent when the refusal names no field. Send the named fields with `upsert_travelers`.

## 0.57.1

Released 2026-10-13.

**Changed**

* A checkout quote is payable for up to one hour. `expires_at` on the checkout answer is at most one hour after the checkout call (it was about fifteen minutes), and earlier when an offer in the trip has a shorter deadline of its own: some airline fares last twenty minutes from their search. A payment submitted after it is still refused with 410 `QUOTE_EXPIRED`.

## 0.57.0

Released 2026-10-13.

This version was first served on 2026-10-13 at 19:34 UTC under the number 1.0.0 and renumbered 0.57.0 at 20:19 UTC; the contract is the same.

**Breaking**

* Supplier fields are removed from responses: `provider` and `provider_name` on flight offers; `provider_statuses` on `flight_search`, in success and error bodies; `provider` on hotels, on `search_scope` and its `providers[]`, and on `hotel_details`, `hotel_cancel` and `flight_refund_preview`. The raw supplier status word is removed too: `provider_status` on `bookings[]` entries and on refund status, and inside the `provider` object of `hotel_cancel_status` and `flight_refund_status`, which keeps `state`, `references` and `documents`.
* `flight_search` no longer answers `result: "provider_error"`. A search that completes without a match answers `result: "no_matches"` as before. A search that could not be completed at all answers an error: `UPSTREAM_REJECTED`, `UPSTREAM_ERROR`, `UPSTREAM_UNAVAILABLE` or `UPSTREAM_TIMEOUT`. Offers that were found are returned even when part of the search failed.
* Servicing inputs that named the supplier or its references are removed: `provider` on the flight refund and exchange requests and on `hotel_cancel`, `order_id`, `provider_reference` and `provider_booking_id`. A request carrying one is refused with 422 `BAD_REQUEST` naming the field. Name the booking with `booking_ref`, which is now required, and the item from `get_booking` (`booked_item_id`, named `item_id` until 0.74.0).
* The two `enable_*` query parameters on `hotel_search` that turned individual hotel suppliers on or off are removed. A request carrying either is refused with 422 naming the parameter.

**Changed**

* `payment_types` on a hotel rate is a closed enum: `pay_now`, `pay_at_property`, `deposit`.
* `hotel_ref` and an ancillary `offer_id` are opaque. Pass them back exactly as received; their format is not part of the contract. `hotel_details` takes a `hotel_ref` as received, or a plain `hotel_id`. `hotel_ref` was later removed in 0.72.0.
* The supplier-facing error codes carry fixed messages. `UPSTREAM_REJECTED`, `UPSTREAM_UNAVAILABLE`, `UPSTREAM_TIMEOUT`, `UPSTREAM_ERROR`, `RATE_LIMITED`, `OFFER_UNAVAILABLE` and `OFFER_EXPIRED` always carry the same message and never upstream text; see [Messages on supplier failures](/concepts/errors#messages-on-supplier-failures). A 429 documents `Retry-After`.
* Upstream statuses follow the error categories. A 503 from the platform is answered 503 `UPSTREAM_UNAVAILABLE` with its `Retry-After`, and a 504 is answered 504 `UPSTREAM_TIMEOUT`; both used to be answered 502 `UPSTREAM_ERROR`. A 429 carries the platform's `Retry-After`. A hotel refusal is 502 `UPSTREAM_REJECTED`, as a flight refusal already was.
* Refusals that used to quote the supplier carry fixed sentences: 409 `not_cancellable` ("This booking cannot be cancelled online. Contact Jinko support."), 409 `not_exchangeable` ("This booking cannot be exchanged online. Contact Jinko support."), 400 `invalid_exchange_request` and `partial_exchange_unsupported` (uppercase since 0.74.0) from the supplier ("This exchange cannot be processed as requested. Change the selected flights or contact Jinko support."), and `manual_reason` where it would be the supplier's own words ("This request needs manual handling by Jinko support.").
* The other supplier refusals carry fixed sentences too: 400 on `flight_search` (search and price check) and on `hotel_search` when a supplier declined the inputs ("The request was declined for these inputs. Check the dates, places and travellers, then retry."), 422 on `car_search` ("The rental request was declined. Try another vehicle, location or time."), and a quote's `failure_message` where it would carry the supplier's words ("The booking could not be completed. Contact Jinko support."). `warnings` on `refund_check` and on the exchange answers leave out notes the supplier sends as free text without a code.
* `flight_price_advice` answers a refusal of its inputs as 400 or 422 `BAD_REQUEST` with the platform's sentence. It used to answer 502 `UPSTREAM_REJECTED`.
* Descriptions and examples in the contract no longer name a supplier.


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