> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gojinko.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Money & prices

> Jinko returns monetary amounts in minor units. Divide by 10 ** decimal_places before displaying, or prices come out 100x too high.

Most monetary amounts Jinko returns — fares, monitoring prices, refunds,
booking totals — are integers in **minor units**, with the scale alongside:

```json theme={null}
"total": { "value": 15977, "currency": "USD", "decimal_places": 2 }
```

A few endpoints (trip and checkout totals, hotel rate amounts) instead return
plain decimals in major units with **no** `decimal_places` field. The
[discriminator rule below](#one-rule-decimal_places-is-the-discriminator)
covers both.

<Warning>
  **`value` is not a display value.** Convert it before showing it to anyone:

  ```
  display = value / 10 ** decimal_places
  ```

  `15977` with `decimal_places: 2` is \*\*$159.77** — not $15,977.
</Warning>

This is the single most common integration mistake with the Jinko API, and it is
easy to miss: `15977` is a plausible long-haul business fare, so a page full of
wrong prices looks like real data rather than a unit bug.

## Convert it

<CodeGroup>
  ```typescript TypeScript theme={null}
  // Last-resort fallback ONLY (a { value } object should always carry
  // decimal_places): the currency's ISO 4217 minor-unit digits.
  const ISO_MINOR_DIGITS: Record<string, number> = {
    JPY: 0, KRW: 0, VND: 0, CLP: 0,
    BHD: 3, JOD: 3, KWD: 3, OMR: 3, TND: 3,
  };
  const minorUnitDigits = (currency: string) => ISO_MINOR_DIGITS[currency] ?? 2;

  // The one rule: decimal_places present -> integer minor units (divide);
  // absent -> already major units. Works for value- and amount-named fields.
  function toDisplayAmount(money: {
    value?: number;
    amount?: number;
    currency: string;
    decimal_places?: number;
  }): number {
    const raw = money.value ?? money.amount ?? 0;
    if (money.decimal_places != null) {
      return raw / 10 ** money.decimal_places;
    }
    // No decimal_places: an { amount, currency } object is already major units.
    // A { value } object without decimal_places should not occur — fall back to
    // the currency's ISO 4217 digits defensively (never a hardcoded 2).
    return money.amount != null ? raw : raw / 10 ** minorUnitDigits(money.currency);
  }

  // Formatting for humans: let Intl apply the currency's own rules.
  function formatMoney(money: Parameters<typeof toDisplayAmount>[0]): string {
    return new Intl.NumberFormat(undefined, {
      style: 'currency',
      currency: money.currency,
    }).format(toDisplayAmount(money));
  }

  formatMoney({ value: 15977, currency: 'USD', decimal_places: 2 }); // "$159.77"
  formatMoney({ value: 18000, currency: 'JPY', decimal_places: 0 }); // "¥18,000"
  formatMoney({ amount: 2594.49, currency: 'EUR' }); // "€2,594.49" (already major)
  ```

  ```python Python theme={null}
  from decimal import Decimal

  # Last-resort fallback ONLY (a value-shaped object always carries
  # decimal_places): the currency's ISO 4217 minor-unit digits.
  _ISO_MINOR_DIGITS = {
      "JPY": 0, "KRW": 0, "VND": 0, "CLP": 0,
      "BHD": 3, "JOD": 3, "KWD": 3, "OMR": 3, "TND": 3,
  }

  def minor_unit_digits(currency: str) -> int:
      return _ISO_MINOR_DIGITS.get(currency, 2)

  def to_display_amount(money: dict) -> Decimal:
      # decimal_places present -> integer minor units; absent -> already major.
      raw = money.get("value", money.get("amount", 0))
      if "decimal_places" in money and money["decimal_places"] is not None:
          # Decimal, not float: binary floats cannot represent every cent exactly.
          return Decimal(raw) / (10 ** money["decimal_places"])
      if "amount" in money:
          return Decimal(str(raw))  # already major units
      return Decimal(raw) / (10 ** minor_unit_digits(money["currency"]))

  to_display_amount({"value": 15977, "currency": "USD", "decimal_places": 2})
  # Decimal('159.77')
  ```
</CodeGroup>

## Not every currency has 2 decimals

`decimal_places` usually matches the currency's ISO 4217 digits:

| Currency                   | `decimal_places` | `value`  | Displays as |
| -------------------------- | ---------------- | -------- | ----------- |
| USD, EUR, GBP, most others | `2`              | `15977`  | 159.77      |
| JPY, KRW, VND, CLP         | `0`              | `18000`  | 18,000      |
| BHD, JOD, KWD, OMR, TND    | `3`              | `159770` | 159.770     |

Hardcoding `/ 100` renders JPY and KRW **100× too small** and Kuwaiti dinar
**10× too high**.

<Warning>
  **Always use the `decimal_places` sent with the amount — don't derive it from
  the currency.** Currency-converted prices (for example cached search results
  converted from USD) can carry a scale that differs from the currency's ISO
  digits. Dividing by the accompanying `decimal_places` is always correct;
  assuming the ISO digits is not. Fall back to ISO digits only when the field
  is genuinely absent.
</Warning>

## One rule: `decimal_places` is the discriminator

Depending on the endpoint, the integer field may be named `value` or `amount`,
and some endpoints return plain decimal numbers. One rule covers everything:

> **If the money object carries `decimal_places`, its number is an integer in
> minor units — divide by `10 ** decimal_places` (whether the field is named
> `value` or `amount`). If there is no `decimal_places` — a bare number with a
> separate currency — it is already in major units: display as-is.**

| Shape you receive                            | Example endpoints                                                          | Scale                 |
| -------------------------------------------- | -------------------------------------------------------------------------- | --------------------- |
| `{ value, currency, decimal_places }`        | flight search/calendar, price monitoring, refunds, exchanges, hotel cancel | Minor — **divide**    |
| `{ amount, currency, decimal_places }`       | `select_ancillaries` totals, booking confirmations                         | Minor — **divide**    |
| `{ amount, currency }` (no `decimal_places`) | trip and checkout totals                                                   | Major — display as-is |
| bare number + separate `currency` field      | trip item prices, hotel rate amounts                                       | Major — display as-is |

`value` and `amount` never appear together on one object — they come from
different backend shapes, not two views of the same number.

<Note>
  **Apply the rule to the actual response object, not to the schema or this
  table.** The OpenAPI schemas mark `decimal_places` as optional on every
  money shape (they describe the union of endpoints), so generated types
  always show `decimal_places?: number` — including for endpoints that never
  send it at runtime, such as checkout totals. Branch on whether the field is
  **present in the response you received**; the endpoint column above only
  describes today's runtime behaviour.
</Note>

## Arithmetic: sum before you divide

When totalling several prices — legs of a trip, travelers in a group, a cart
with flights and hotels — add the **minor-unit integers** and convert once at the
end. Converting first introduces floating-point drift that shows up as totals
being a cent off:

```typescript theme={null}
// Correct: integer arithmetic, one conversion at the end — using the SENT
// scale, never a hardcoded 2.
const dp = items[0].total.decimal_places;
const totalMinor = items.reduce((sum, i) => sum + i.total.value, 0);
const display = totalMinor / 10 ** dp;

// Wrong: 0.1 + 0.2 !== 0.3
const display = items.reduce((sum, i) => sum + i.total.value / 100, 0);
```

Only add amounts that share **both** a `currency` and a `decimal_places` —
Jinko can return different currencies in one response (for example a
multi-provider trip), and converted prices can carry a different scale.
Rescale first if the `decimal_places` differ.

## Common mistakes

|   |                                                                                                                                       |
| - | ------------------------------------------------------------------------------------------------------------------------------------- |
| ❌ | `<span>${'{'}total.value{'}'}</span>` — renders 100× too high                                                                         |
| ✅ | `<span>{'{'}formatMoney(total){'}'}</span>`                                                                                           |
| ❌ | `total.value / 100` — breaks JPY (0 digits) and KWD (3)                                                                               |
| ✅ | `total.value / 10 ** total.decimal_places` — always the dp sent with the amount                                                       |
| ❌ | Summing converted floats                                                                                                              |
| ✅ | Summing minor-unit integers, converting once                                                                                          |
| ❌ | Assuming `amount` is always major units — on objects that carry `decimal_places` (e.g. `select_ancillaries`), `amount` is minor units |
| ✅ | `decimal_places` present → divide; absent → display as-is                                                                             |

## Using the CLI or SDK?

The same rules apply unchanged. `@gojinko/cli` output (JSON is its default
format) and `@gojinko/api-client` responses are the **raw API body** — neither
converts money for you. A `jinko flight-search --format json` result carries the
same `{ value, currency, decimal_places }` objects described above; a
`jinko checkout` total is the same major-units `{ amount, currency }`.

## Prices look 100× too high?

That symptom has exactly one common cause: `value` was rendered without
dividing by `10 ** decimal_places`. Check the raw JSON — if you see
`"value": 15977, "decimal_places": 2` for a fare you expect to be \~\$160, that's
it.

The mirror symptom — prices looking **100× too small** — is usually a hardcoded
`/ 100` applied to a `decimal_places: 0` currency such as JPY.
