@gojinko/api-client
v2.27.0
Published
Typed API client for the Jinko BFF. Methods mirror the MCP tools and CLI commands 1:1.
Readme
@gojinko/api-client
Typed API client for the Jinko BFF. Methods mirror the MCP tools and CLI commands 1:1.
Install
npm install @gojinko/api-clientQuick Start
import { createJinkoClient } from '@gojinko/api-client';
// Uses credentials from ~/.jinko/config.yaml or JINKO_API_KEY env
const client = await createJinkoClient();
// Or pass an API key directly
const client = await createJinkoClient({ apiKey: 'jnk_...' });Authentication
Authenticate with a tenant API key (jnk_...) — get one from
dashboard.gojinko.com → Developers → API keys. The client resolves
credentials automatically in this order:
apiKeyoption passed tocreateJinkoClient()JINKO_API_KEYenvironment variable~/.jinko/config.yaml— API key (managed byjinko auth login --key jnk_...)
OAuth is deprecated. Legacy OAuth tokens still present in
~/.jinko/config.yamlcontinue to auto-refresh, but new logins use API keys.
Methods
The client targets the jinko-api public surface (https://api.gojinko.com,
canonical /v1/* routes). Authenticate with a jnk_ API key (X-API-Key).
Discovery (cached)
| Method | Endpoint |
|---|---|
| client.findFlight() | POST /v1/flight_calendar |
| client.flightCalendar() | POST /v1/flight_calendar |
| client.findDestination() | POST /v1/find_destination |
| client.findDates() | POST /v1/find_dates |
| client.lowestFare() | POST /v1/lowest_fare |
Live search & trip building
| Method | Endpoint |
|---|---|
| client.flightSearch() | POST /v1/flight_search |
| client.hotelSearch() | POST /v1/hotel_search |
| client.hotelDetails() | GET /v1/hotel_details/{hotel_id} |
| client.groundSearch() | POST /v1/ground_search |
| client.trip() | POST /v1/trip |
| client.getTrip(tripId) | GET /v1/trip/{trip_id} |
Checkout
| Method | Endpoint |
|---|---|
| client.checkout(tripId) | POST /v1/checkout |
| client.submitAgentPayment(tripId, token) | POST /v1/agent_payment/submit |
| client.getAncillaries(tripId) | GET /v1/trip/{trip_id}/ancillaries |
| client.selectAncillaries() | POST /v1/select_ancillaries |
| client.book(tripId) (deprecated alias of checkout) | POST /v1/book |
Post-booking
| Method | Endpoint |
|---|---|
| client.getBooking() | POST /v1/get_booking |
| client.refundCheck() | POST /v1/refund_check |
| client.refundCommit() | POST /v1/refund_commit |
| client.refundStatus() | POST /v1/refund_status |
| client.exchangeShop() | POST /v1/exchange_shop |
| client.exchangePrice() | POST /v1/exchange_price |
| client.exchangeCommit() | POST /v1/exchange_commit |
| client.exchangeStatus() | POST /v1/exchange_status |
| client.hotelCancelBooking() | POST /v1/hotel_cancel |
get_bookingreturns the whole booking (flight + hotel). There is no separate hotel-retrieve method on the public surface.
Usage
flight_search — Live search / price-check
Preferred flight entry point. Returns a bookable trip_item_token in one step.
// Search mode — canonical flat body (adults at top level)
const results = await client.flightSearch({
origin: 'PAR',
destination: 'NYC',
departure_date: '2026-06-15',
trip_type: 'oneway',
adults: 1,
});
// Price-check mode (returns trip_item_token)
const fares = await client.flightSearch({
offer_token: 'tok_abc123',
adults: 1,
});hotel_search — Live hotel search
Returns hotels with rooms + rates. Each rate's offer_id (an htl_* token) plugs into trip(add_item) exactly like a flight trip_item_token — flights and hotels share one cart.
const results = await client.hotelSearch({
city_name: 'Paris',
country_code: 'fr',
checkin: '2026-07-15',
checkout: '2026-07-18',
adults: 2,
});
// → { hotels: [ { hotel_id, name, star_rating, rooms: [ { rates: [ { offer_id: 'htl_...', total_amount, ... } ] } ] } ] }
// Pick a rate's offer_id and add to a trip — same call shape as flights:
await client.trip({ add_item: { trip_item_token: 'htl_xxx:rate_yyy' } });ground_search — Live rail, coach and ferry search
Returns ground connections (Distribusion). Two things to know before your first call:
- Codes are ISO-country + city, not IATA —
GBLONfor London, notLON. A wrong code returns no inventory rather than an error, which is a confusing way to fail. connections[].idis the trip item token. Unlike hotels it carries nohtl_-style prefix — pass it verbatim totrip().
const results = await client.groundSearch({
departure_city: 'GBLON',
arrival_city: 'GBLON',
departure_date: '2026-08-28',
passengers: [{ pax: 1 }], // omit max_age for an adult; 0-15 prices as a child
currency: 'GBP',
});
// → { connections: [ { id, departure_station, arrival_station, departure_time,
// duration_minutes, transport_mode, marketing_carrier,
// fares: [ { fare_class, total_price, refundable } ] } ] }
// Add the journey to a trip — the id goes in unmodified:
await client.trip({ add_item: { trip_item_token: results.connections[0].id } });Pass return_date for a round trip. Ground shares the cart with flights and
hotels, so checkout() and getTrip() need no ground-specific handling.
trip — Create trip, add flight or hotel, set travelers
const trip = await client.trip({
add_item: { trip_item_token: 'tok_xyz' },
upsert_travelers: {
travelers: [{
first_name: 'Jane',
last_name: 'Doe',
date_of_birth: '1990-01-15',
gender: 'FEMALE',
passenger_type: 'ADULT',
// Optional per-traveler loyalty program. airline = IATA code of the
// program issuer (e.g. 'LH'), not necessarily the operating carrier.
frequent_flyer: { airline: 'LH', number: '992100100' },
// Optional US trusted-traveler identifiers (flights only). The issuing
// country defaults to US; a redress_number works the same way.
known_traveler_number: '998765432',
}],
contact: { email: '[email protected]', phone: '+33612345678' },
},
});
// → { trip_id, ... }checkout — Finalize a trip (two payment paths)
checkout returns BOTH the hosted checkout_url (a human opens it to pay) AND
agent_spt_params (an agent uses them to pay programmatically). (book() is a
deprecated alias of checkout().)
const checkout = await client.checkout('42');
// {
// session_id, checkout_url, expires_at, status,
// total_amount: { amount, currency },
// items: [ ... ],
// agent_spt_params: { max_amount, currency, stripe_profile?, expires_at? },
// }
// Human path: send the user to checkout.checkout_url to complete payment.submitAgentPayment — pay programmatically with a Shared Payment Token
Mint a Shared Payment Token scoped to checkout.agent_spt_params, then submit it
with the trip_id. The BFF schedules the agent fulfillment and redeems the token
server-side. On a 3DS step-up / decline the result carries a checkout_url
fallback instead of advancing the booking.
const result = await client.submitAgentPayment('42', 'spt_1Nq8L2eZvKYlo2C0');
// { status: 'processing', payment_verified: true, booking_ref: 'JNK-XEVGW5', fulfillment_cart_id, ... }
// or, on a 3DS step-up: { payment_verified: false, checkout_url } (no booking_ref)booking_ref is the Jinko booking reference (JNK-…) — the handle
getBooking(), hotelCancelBooking(), refund and exchange take as
booking_ref. An agent that pays this way never opens checkout_url, so this
response (and getTrip() afterwards) is where it learns the reference.
selectAncillaries — Add baggage, seats, meals
await client.selectAncillaries({
trip_id: '42',
item_id: 'item_1',
selections: [
{ offer_id: 'offer_bag_checked_1', quantity: 1 },
{ offer_id: 'offer_seat_12A', pax_ref_id: 'pax_1', quantity: 1 },
],
});getTrip — Full lifecycle state
const status = await client.getTrip('42');
// {
// trip_id, status,
// travelers, contact, items, total_amount,
// quote: { quoted_cart_id, status, expires_at },
// fulfillment: { fulfillment_cart_id, status, phase, scheduled_at },
// booking_ref: 'JNK-XEVGW5', // the Jinko reference — present once fulfillment is scheduled
// bookings: [ { item_id, booking_reference, pnr, provider_status } ],
// created_at, updated_at,
// }Poll getTrip() until status === 'fulfilled' (or 'partially_fulfilled') to read the PNRs.
booking_ref (top level, one per trip) is the Jinko booking reference — what
getBooking(), hotelCancelBooking(), refund and exchange take as
booking_ref. bookings[].booking_reference is the SUPPLIER confirmation
(airline record locator, hotel confirmation number) and is not that handle.
refundCheck / refundCommit
Two auth modes: guest (booking_ref + last_name) or authenticated (order_id).
const eligibility = await client.refundCheck({ booking_ref: 'JNK-ABC123', last_name: 'Doe' });
const refund = await client.refundCommit({ booking_ref: 'JNK-ABC123', last_name: 'Doe' });Token Flow
findFlight / findDestination / flightCalendar → offer_token (cached, flights)
flightSearch (offer_token) → trip_item_token (live, flights)
hotelSearch → htl_* offer_id (live, hotels)
groundSearch → connections[].id (live, ground; raw, NOT prefixed)
trip (trip_item_token OR htl_* OR ground id) → trip_id (multi-domain)
checkout (trip_id) → checkout_url + agent_spt_params + items
├─ [human] user pays on checkout_url
└─ [agent] submitAgentPayment(trip_id, spt) → authorized server-side
getTrip (trip_id) → bookings / PNRsQuote is automatic — it's handled internally by checkout(). You don't call it.
Error Handling
import { createJinkoClient, ApiError, AuthError } from '@gojinko/api-client';
try {
const client = await createJinkoClient();
} catch (error) {
if (error instanceof AuthError) {
// No credentials configured — run `jinko auth login --key jnk_...`
}
if (error instanceof ApiError) {
console.error(error.code, error.message, error.statusCode);
}
}Types
All request/response types are derived from the canonical public-api.yaml
contract (regenerated into src/types/generated.ts):
import type {
FlightDiscoveryRequest,
FindDestinationRequest,
FlightSearchRequest,
HotelSearchRequest,
HotelDetailsRequest,
TripRequest,
BookRequest,
BookResponse,
GetTripResponse,
AncillaryRequest,
BookingGetRequest,
BookingGetResponse,
RefundCheckRequest,
RefundCommitRequest,
Traveler,
Money,
} from '@gojinko/api-client';The generated paths and components from the OpenAPI spec are also exported
for advanced use.
Reading money
Money objects come in two shapes that share field names, and decimal_places
is what tells them apart (docs.gojinko.com/concepts/money).
readMoney applies that rule so you don't divide by 100 on the wrong shape:
import { readMoney, tryReadMoney, MoneyShapeError } from '@gojinko/api-client';
const { itineraries } = await client.flightCalendar({
origins: ['CDG'],
destinations: ['JFK'],
departure_date_ranges: [{ start: '2026-10-01', end: '2026-10-31' }],
trip_type: 'oneway',
});
const total = readMoney(itineraries[0].total);
// { currency: 'USD', scale: 'minor', decimalPlaces: 2, minor: 33000,
// amount: 330, text: '330.00', display: 'USD 330.00' }
console.log(total.display ?? `${total.currency} ${total.text}`); // "USD 330.00"decimal_placespresent → the figure is an integer in minor units:amountis the figure / 10^decimal_places.decimal_placesabsent → anamountis already major units. A barevalueis read at the currency's ISO 4217 digits (JPY 0, KWD 3, most others 2), never an assumed 2.amountandvalueboth present → the non-zero one.textis exact decimal text built with integer arithmetic, not float rounding.displayis the server's own string, when the response carries one.- An unexpected shape (no currency, a fractional minor figure, two figures that
disagree, …) throws
MoneyShapeError.tryReadMoneyreturnsundefinedinstead.
Raw Client
The underlying openapi-fetch client is still accessible and is typed against the
jinko-api /v1/* surface:
const response = await client.raw.POST('/v1/flight_search', {
body: { origin: 'JFK', destination: 'LAX', departure_date: '2026-09-01' },
});