Prerequisites
- A Jinko account and an API key (
jnk_...). Get one. - 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) Discover flights
Start with discovery. It is cached, fast, and broad.- SDK
- CLI
- MCP
2) Live pricing
Discovery returns cached prices. Before booking, confirm them withflight_search.
On REST, the SDK and the CLI, flight_search is a flat request with two mutually exclusive modes. Pick one per request:
The response is an
offers[] list, and each offer carries a fares[] array. A fare’s trip_item_token is ready to drop straight into a trip, no assembly required.
- SDK
- CLI
- MCP
flight_unavailable, the flight sold out between discovery and now, and the alternatives[] field has replacements. Loop back to discovery or present them to the user.
3) Build the trip
Now you have a livetrip_item_token. Add it to a trip AND set travelers in one call:
- SDK
- CLI
- MCP
An unpriced item now has no
price key at all. It used to serialize as price: 0 with currency: "", which reads as “this is free” to anything doing arithmetic. Absence now means not priced yet, and a price appears once the trip is quoted. Check for the key before reading it, and never treat a missing price as zero.4) Quote and select ancillaries (optional)
If you want to preselect bags, seats, or meals before checkout, list what’s available withgetAncillaries (it surfaces the ancillaries without generating a checkout URL), then pre-select. (Make sure travelers are set first; ancillaries are priced per passenger.)
- SDK
- CLI
- MCP
5) Checkout
Create the Stripe checkout session:- SDK
- CLI
- MCP
checkout_url points at app.gojinko.com/checkout, a Stripe-hosted page Jinko owns. The query string carries a signed capability token (?t=<token>).
On REST / CLI / SDK, On MCP, the booking widget drives this interactively instead of returning the envelope. Either way, fulfillment happens asynchronously after the user pays. See steps 7 to 8.
checkout is synchronous: it schedules the quote, polls until it’s ready, and returns the full envelope in one call:payment_type tells you how this cart will actually be paid, and is one of intent, checkout or agent. It reflects the live authorization, not what you asked for when you created the trip.
Two clocks, don’t confuse them
expires_at is the price deadline, not the link deadline.The link outlives the price, deliberately: a customer who opens the page 20 minutes later still reaches it. Only
expires_at is on the wire; the 4-hour link TTL is not a field you can read.After expires_at: refused, not re-priced
Once the quote’s expires_at has passed, the platform will not spend your customer’s money on that price. What happens next depends on who is paying.
Paying agentically. POST /v1/agent_payment/submit answers 410 QUOTE_EXPIRED and creates no payment object at all. Nothing is authorized, nothing is captured, and the trip and its quote are untouched. The response carries the deadline it enforced:
checkout again on the same trip (an expired quote is never reused, so this re-quotes and hands you a fresh expires_at, total, and agent_spt_params), then mint a new Shared Payment Token against those new params and submit that. Check price_changed on the new response before you do, because a re-quote is exactly when a price moves.
A human on the checkout page. The page handles it without you. It reads the quote deadline, and once it has passed it offers a Refresh price button that re-quotes in place, re-renders the total with any moved item highlighted, and then lets the customer pay. They never see a stale price and never have to start over.
If the price moved
Airlines re-price constantly, and a quote is only good for 5 minutes. By defaultcheckout proceeds at the airline’s current price. It does not fail, and there is no tolerance threshold. It tells you instead, on the affected items[] entry:
fail_on_price_change: true in the checkout request body. A moved price then returns 409 price_changed listing the affected items, with nothing charged and the trip still quoted:
error.code === "PRICE_CHANGED". It is the standard three-key error envelope this API uses everywhere, so your generic handler already understands it, and a price refusal is always a 409, never a 502.
fail_on_price_change is an ordinary request-body field, so a generated SDK client exposes it like any other checkout parameter. It defaults to false; leave it out and you keep the proceed-at-the-new-price behaviour.
On a 409, show the new price to your customer, then call checkout again without the flag to accept it, or re-run live pricing to look for something cheaper.
Full detail in Errors → The price moved.
6) User pays
Send the user tocheckout_url. They:
- Confirm the itinerary.
- Pick ancillaries (if not pre-selected).
- Enter payment.
- Stripe holds the authorization.
7) 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):
fulfillment is absent until the customer pays. POST /v1/checkout does not create a fulfillment record. It quotes the cart and hands you a checkout_url. The record appears, at awaiting_payment, when the customer presses Pay on that page. Before then get_trip returns quote but no fulfillment and no bookings, so read trip.fulfillment?.status defensively.Two other values you may see elsewhere and should not confuse with these. pending is an item-level status and never appears as a trip’s fulfillment.status. fulfilling is a value of the trip’s own top-level status field, not of fulfillment.status, despite what older versions of this page said.8) Watch the booking land
Pollget_trip until fulfillment.status is terminal:
- SDK
- CLI
- MCP
bookings[] looks like this. Note the field is booking_reference, not booking_ref, and pnr is optional even at completed, because not every provider issues one:
trip_item_token and offer_id on a flight; offer_id and hotel_id on a hotel), so you can reconcile a finished booking against your own search results without a side map. See the identity echo for the caveats.
bookings[] lists failed items too, so read provider_status per entry rather than assuming every entry is a confirmed booking. (booking_ref is a real field name elsewhere in the API, on the guest-authenticated post-booking endpoints such as get_booking and hotel_cancel. It is just not the name used inside bookings[].)
What’s next?
- Add a hotel to the trip: see the Flight + Hotel guide for one trip with two items.
- Hotel-only booking: see the Hotel booking guide.
- Refund a booking: Refund flow,
refund-checkthenrefund-committhenrefund-status. - Exchange dates: Exchange flow, a four-step variant of the booking flow.
- Troubleshooting: Errors has the full status-code reference.
