Skip to main content
This guide walks you through booking a real flight from start to finish. The same flow works regardless of interface, so each step shows three variants: SDK, CLI, and MCP. The one-line version of the flow:
discovery → flight_search → trip(add_item + travelers) → book → user pays → get_trip

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.
import { createJinkoClient } from '@gojinko/api-client'

const client = await createJinkoClient({ apiKey: process.env.JINKO_API_KEY })

const result = await client.findFlight({
  origin: 'JFK',
  destination: 'CDG',
  trip_type: 'roundtrip',
  departure_dates: ['2026-06-15'],
  return_dates: ['2026-06-22'],
})

const candidate = result.results[0]
console.log(candidate.offer_token, candidate.price)

2) Live pricing

Discovery returns cached prices. Before booking, confirm them with flight_search. flight_search is a flat request: origin, destination, and departure_date are required (return_date for round-trips). To re-price a specific candidate from discovery, also pass its offer_token — but the route and dates are still required alongside it. 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.
const priced = await client.flightSearch({
  origin: 'JFK',
  destination: 'CDG',
  departure_date: '2026-06-15',
  return_date: '2026-06-22',
  trip_type: 'roundtrip',
  offer_token: candidate.offer_token, // optional: re-price the discovered offer
})

const offer = priced.offers[0]
const tripItemToken = offer.fares[0].trip_item_token
If the response status is 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 live trip_item_token. Add it to a trip AND set travelers in one call:
const trip = await client.trip({
  add_item: { trip_item_token: tripItemToken },
  upsert_travelers: {
    travelers: [{
      first_name: 'Jane',
      last_name: 'Doe',
      date_of_birth: '1990-01-15',
      gender: 'FEMALE',
      passenger_type: 'ADULT',
    }],
    contact: {
      email: 'jane@example.com',
      phone: '+33612345678',
    },
  },
})
const tripId = trip.trip_id

4) Quote and select ancillaries (optional)

If you want to preselect bags, seats, or meals before checkout, list what’s available with getAncillaries — it surfaces the ancillaries without generating a checkout URL — then pre-select. (Make sure travelers are set first; ancillaries are priced per passenger.)
// List ancillaries — runs a price quote behind the scenes, no checkout URL.
let ancillaries = await client.getAncillaries(tripId)

// The quote may still be pricing; poll until it's ready.
while (ancillaries.status === 'pricing') {
  await new Promise(r => setTimeout(r, (ancillaries.retry_after_seconds ?? 2) * 1000))
  ancillaries = await client.getAncillaries(tripId)
}

// Pick an ancillary offer
const item = ancillaries.items[0]
const bagOffer = item.available_ancillaries.find(a => a.type === 'bag')
await client.selectAncillaries({
  trip_id: tripId,
  item_id: item.item_id,
  selections: [{ offer_id: bagOffer.offer_id }],
})
You don’t have to pre-select. Your user can pick on the Stripe checkout page. Skip this step if you want the simplest flow.

5) Checkout

Create the Stripe checkout session:
const { checkout_url } = await client.book(tripId)
console.log('Open in browser:', checkout_url)
The checkout_url points at app.gojinko.com/checkout, a Stripe-hosted page Jinko owns.
On REST / CLI / SDK, book is synchronous: it schedules the quote, polls until it’s ready, and returns the full checkout envelope in one call — { session_id, checkout_url, status: "ready", total_amount, items }. (On MCP, the booking widget drives this interactively instead of returning the envelope.) Either way, fulfillment still happens asynchronously after the user pays — see steps 7–8.

6) User pays

Send the user to checkout_url. They:
  1. Confirm the itinerary.
  2. Pick ancillaries (if not pre-selected).
  3. Enter payment.
  4. 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):
StateMeaning
pendingPayment cleared, booking not yet attempted
fulfillingCalling the airline or provider (takes seconds to hours)
completedBooking confirmed, pnr and booking_ref populated
failedProvider rejected (rare). Refund is issued automatically.

8) Watch the booking land

Poll get_trip until fulfillment.status is terminal:
async function waitForBooking(client, tripId: string) {
  while (true) {
    const trip = await client.getTrip(tripId)
    if (trip.fulfillment?.status === 'completed') {
      console.log('Booked! Ref:', trip.bookings[0].booking_ref)
      return trip
    }
    if (trip.fulfillment?.status === 'failed') {
      throw new Error('Booking failed, refund in progress')
    }
    await new Promise(r => setTimeout(r, 5000))
  }
}

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-check then refund-commit then refund-status.
  • Exchange dates: Exchange flow, a four-step variant of the booking flow.
  • Troubleshooting: Errors has the full status-code reference.