<major>.<minor>.<patch>. Every change, including a removal, moves the minor or patch number; the major number does not move. The number lives in info.version of the OpenAPI document, and it is the one signal that the contract moved.
The rules
1
Additive changes bump the minor version, with no notice
A new optional field, a new endpoint, a new event type, a new error code, a new enum value on a field you send us: all of these ship in a minor bump with no advance notice. Your integration keeps working through them, which is why they need none.
2
Removals ship as a new minor version, on the same path
Removing a field or an input, making an optional input required, or narrowing a type is a breaking change. It ships as a new minor contract version on the same
/v1 path. The version is announced under Contract changes on this page and in the changelog, at the latest when it is released, with every field and input it removes. Read that list for every new version before you upgrade.3
A change of meaning ships on a new path
We do not repurpose a field or change what an existing request does under
/v1. A change that alters the meaning of existing requests or responses goes to a new path (/v2), and the two run side by side for at least 90 days so you can move at your own pace.4
Deprecations are announced on the wire, where you cannot miss them
A route on its way out keeps answering, and every one of its responses carries headers that name its replacement and its removal date. You do not have to be reading announcements to find out: your own logs tell you, well before anything stops working.
What counts as breaking
Deprecation headers
While a route is deprecated but still answering, every response it sends carries three headers:
They are set before anything else runs, so a 401 carries them just as a 200 does. That is deliberate: a caller whose credentials are stale is exactly the caller most likely to be running old code.
The first route to carry them is
POST /v1/book, superseded by POST /v1/checkout:
/v1/book, switch to /v1/checkout. It is the same operation under the name the rest of the platform uses, and it is where new behaviour lands.
Deprecated fields
A response field can be deprecated while its route stays. The field keeps being sent with the same meaning, its replacement is sent beside it, and the OpenAPI document marks the old fielddeprecated: true with a description that names the replacement (“Deprecated: use <new> instead.”). The Deprecation and Sunset headers describe whole routes, so a route that only has deprecated fields does not send them.
Removing a deprecated field is a breaking change and follows the rules above. Until a removal date is announced, reading the old field keeps working; move to the replacement when you next change that code.
Contract changes
Each contract version that deprecates, removes or changes something is described here, oldest first. A version that removes something lists every field and input it removes.Changes in 0.26.0
Contract 0.26.0 gives each concept one field name and every money figure one shape. Nothing is removed and no removal date is set.
- Money. Every figure is available in the money shape
{ value, currency, decimal_places, display }, withvaluein minor units: in a*_moneyfield beside the older field (total_amount_money,price_money,max_amount_money, …), asvalueinside objects that carried the same integer asamount, or asamount_moneyinside objects that hold money beside other members (a cancellation preview’srefund, a hotel tax line). The older fields, including acurrencyor*_displaysibling that only described them, are deprecated. Hotel cancellation steps addpercentandnightsfor steps that are not a sum. Money & prices maps every field. get_bookingbooking.total_paid. Now sent: the gross amount captured for the booking, refunds not subtracted. Absent when nothing was captured or when the booking was charged in more than one currency.- Cancellation and refund commits.
acknowledgedcan be copied from the preview’srefund.amount_money. The older inline figure is still accepted. - Webhooks.
booking.partialaddscaptured_amount_moneyandcaptured_total_money(the major-unitcaptured_amountandcaptured_totalare deprecated).servicing.completedis documented, withrefundandpenaltyin the money shape. See Webhooks. POST /v1/tripitems.kindis added besidetype, which is deprecated.kindis the nameGET /v1/trip/{trip_id}and checkout already use.- Hotel rooms.
room_capacityis added besidemax_occupancyonhotel_searchandhotel_detailsrooms;max_occupancyis deprecated. Onhotel_searchit repeated the requested occupancy and never was the room’s capacity. - A live flight search with no matches answers 200 with
offers: [],result: "no_matches"and aninstruction. TheX-Jinko-ResultandX-Jinko-Instructionheaders are still sent and are deprecated.
Changes in 0.33.0
- A live flight search with provider failures and no offers answers 200 with
result: "provider_error"and an instruction to retry without changing filters;provider_statuseserror rows can carry an optionalreason: { code, message }, even when other sources return offers.
Changes in 0.39.0
Contract 0.39.0 states a flight exchange in the customer’s own figures. Nothing is removed and no removal date is set.
POST /v1/exchange_pricecustomer_amounts. Added:original_fare(what the customer paid for the ticket),new_fare(the new ticket’s value),penalty_amount(the airline’s change fee, absent when there is none) andtotal_dueortotal_refund(exactly what is charged or refunded), each in the money shape. They add up:original_fare + total_due − total_refund = new_fare + penalty_amount.customer_amountsis absent when the platform cannot state the customer’s figures.POST /v1/exchange_pricefare_difference,original_fare,new_fareandpenalty_amount. Deprecated: usecustomer_amounts.*instead. They are the supplier’s figures and do not add up to what is charged or refunded. They keep their values.total_due,total_refundandpayment_outcomekeep their meaning: what the customer pays or gets back. On a booking whose supplier adds its own service fees, they now leave those fees out, since the customer is not charged them, andpayment_outcomefollows what the customer pays or gets back.POST /v1/exchange_shopestimated_amountfollows the same rule. It now leaves out the supplier’s own service fees, so the estimate states what the customer would pay./v1/exchange_pricestays the binding figure.- Exchange price and flight cancellation can now answer
manual_required: an operator completes them when the supplier’s figures cannot be stated safely. This covers components that cannot be classified or do not add up, an airline change fee charged outside the exchange total, inconsistent supplier totals, or a cancellation whose supplier booking fee cannot be established or that follows a completed exchange.
Changes in 0.57.0
Contract 0.57.0 is a minor version on the same /v1 paths that removes fields and inputs: responses, identifiers and error messages no longer say which supplier an offer, a booking or an error came from. The removals are breaking changes; read the list below before you upgrade.
Removed:
- Supplier fields on responses.
providerandprovider_nameon flight offers;provider_statusesonflight_search, in success and error bodies;provideron hotels, onsearch_scopeand itsproviders[], and onhotel_details,hotel_cancelandflight_refund_preview. The raw supplier status word is gone too:provider_statusonbookings[]entries and on refund status, and inside theproviderobject ofhotel_cancel_statusandflight_refund_status, which keepsstate,referencesanddocuments. result: "provider_error"onflight_search. A search that completes without a match answersresult: "no_matches"as before. A search that could not be completed at all answers an error (UPSTREAM_REJECTED,UPSTREAM_ERROR,UPSTREAM_UNAVAILABLEorUPSTREAM_TIMEOUT). Offers that were found are returned even when part of the search failed.- Servicing inputs that named the supplier or its references.
provideron the flight refund and exchange requests and onhotel_cancel,order_id,provider_referenceandprovider_booking_id. A request carrying one is refused with 422BAD_REQUESTnaming the field; name the booking withbooking_ref(now required) anditem_idfromget_booking. - The two
hotel_searchquery switches that turned individual hotel suppliers on or off. A request carrying either is refused with 422 naming the parameter.
payment_typeson a hotel rate is a closed enum:pay_now,pay_at_property,deposit.hotel_refand an ancillaryoffer_idare opaque. Pass them back exactly as received; their format is not part of the contract.hotel_detailstakes ahotel_refas received, or a plainhotel_id.- Fixed messages for the supplier-facing error codes.
UPSTREAM_REJECTED,UPSTREAM_UNAVAILABLE,UPSTREAM_TIMEOUT,UPSTREAM_ERROR,RATE_LIMITED,OFFER_UNAVAILABLEandOFFER_EXPIREDalways carry the same message and never upstream text; see Messages on supplier failures. A 429 documentsRetry-After. - Upstream statuses follow the error categories. A 503 from the platform is answered 503
UPSTREAM_UNAVAILABLEwith itsRetry-After, and a 504 is answered 504UPSTREAM_TIMEOUT; both used to be answered as 502UPSTREAM_ERROR. A 429 carries the platform’sRetry-After. A hotel refusal is 502UPSTREAM_REJECTED, as a flight refusal already was. - Refusals that used to quote the supplier carry fixed sentences. 409
not_cancellable, 409not_exchangeable, 400invalid_exchange_requestandpartial_exchange_unsupportedfrom the supplier, andmanual_reasonwhere it would be the supplier’s own words, each carry one fixed sentence; see Messages on supplier failures. flight_price_adviceanswers a refusal of its inputs as 400 or 422BAD_REQUESTwith the platform’s sentence. It used to answer 502UPSTREAM_REJECTED; price advice reaches no supplier, so the refusal is the caller’s to fix.
The error-code enum is closed
error.code is a fixed set, published as an enum in the spec. The platform sends no code outside it, and every code in it has a producer. The spec and the errors page are kept in step with the platform’s own table, so the list you read is the list you can receive.
Adding a code is additive and lands in a minor bump, so keep a default branch for a code you do not recognise. A code can also stop being sent: it leaves the set in a minor version, named under Contract changes, and a branch you wrote for it goes unreached rather than wrong.
What we deliberately do not do
- No dated versions in a request header. There is no
Jinko-Versionheader to pin. The contract version is a property of the document you read, not of the call you make. - No silent behaviour changes inside a version. If the answer to the same request changes in a way you can observe, the minor version moves, so
info.versionis enough to tell you something did. - No route removals without the sunset window. A route that carries no
Sunsetheader is not going anywhere. - No unannounced removals. A field or an input is removed only in a version listed on this page, with the removal named.
Where to look
- The served contract:
info.versionin the OpenAPI document. It is the version of the environment you fetched it from. - What changed: the contract version itself is the signal. Compare
info.versionagainst the one you last integrated against, and read a deprecated route’sDeprecationandSunsetheaders straight off your own responses. Deprecated fields are marked in the OpenAPI document. Deprecations, removals and other changes are listed by version under Contract changes. - What a code means: Errors & troubleshooting carries the full enum with a remedy for each entry.
