npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-client

Quick 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:

  1. apiKey option passed to createJinkoClient()
  2. JINKO_API_KEY environment variable
  3. ~/.jinko/config.yaml — API key (managed by jinko auth login --key jnk_...)

OAuth is deprecated. Legacy OAuth tokens still present in ~/.jinko/config.yaml continue 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_booking returns 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:

  1. Codes are ISO-country + city, not IATAGBLON for London, not LON. A wrong code returns no inventory rather than an error, which is a confusing way to fail.
  2. connections[].id is the trip item token. Unlike hotels it carries no htl_-style prefix — pass it verbatim to trip().
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 / PNRs

Quote 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_places present → the figure is an integer in minor units: amount is the figure / 10^decimal_places.
  • decimal_places absent → an amount is already major units. A bare value is read at the currency's ISO 4217 digits (JPY 0, KWD 3, most others 2), never an assumed 2.
  • amount and value both present → the non-zero one.
  • text is exact decimal text built with integer arithmetic, not float rounding. display is 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. tryReadMoney returns undefined instead.

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' },
});