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

# Money and amounts

> Every monetary field is an integer in minor units, with a per-currency exponent.

Every monetary field in this API ends in `_minor`. The suffix is part of the
contract, not decoration: it declares that the value is an **integer count of
the currency's smallest unit** — never a decimal, never a float.

A field named `amount` could be read as `20.00` or `2000`. A field named
`amount_minor` can only be read one way.

```json theme={null}
{ "amount_minor": 500000, "currency": "UGX" }
```

That is 500,000 Ugandan shillings, not 5,000.

## The exponent is per currency

Four of the currencies AptaPay supports are **zero-decimal**: they have no
minor unit at all. One UGX is one UGX, not 100 cents.

| Exponent             | Currencies                                                    |
| -------------------- | ------------------------------------------------------------- |
| **0** — zero-decimal | `UGX`, `RWF`, `XAF`, `XOF`                                    |
| **2**                | `KES`, `GHS`, `NGN`, `TZS`, `ZMW`, `ZAR`, `USD`, `EUR`, `GBP` |

<Warning>
  Assuming two decimals everywhere is a **100× error** in UGX, RWF, XAF and
  XOF. This is the single most expensive mistake available in this API. The
  gateway never defaults an exponent — an unrecognised currency is rejected
  with `unknown_currency` rather than guessed.
</Warning>

## Converting

```typescript money.ts theme={null}
const EXPONENTS: Record<string, number> = {
  UGX: 0, RWF: 0, XAF: 0, XOF: 0,
  KES: 2, GHS: 2, NGN: 2, TZS: 2, ZMW: 2, ZAR: 2,
  USD: 2, EUR: 2, GBP: 2,
};

function exponentFor(currency: string): number {
  const e = EXPONENTS[currency];
  if (e === undefined) throw new Error(`Unknown currency ${currency}`);
  return e;
}

/** "1234.56" USD -> 123456   |   "2000" UGX -> 2000 */
export function toMinor(major: string, currency: string): number {
  const exponent = exponentFor(currency);
  const [whole, frac = ""] = major.split(".");
  if (frac.replace(/0+$/, "").length > exponent) {
    throw new Error(`${major} has more precision than ${currency} allows`);
  }
  return Number(whole + frac.padEnd(exponent, "0").slice(0, exponent));
}

/** 123456 USD -> "1234.56"   |   2000 UGX -> "2000" */
export function toMajor(minor: number, currency: string): string {
  const exponent = exponentFor(currency);
  if (exponent === 0) return String(minor);
  const digits = String(Math.abs(minor)).padStart(exponent + 1, "0");
  const cut = digits.length - exponent;
  return `${minor < 0 ? "-" : ""}${digits.slice(0, cut)}.${digits.slice(cut)}`;
}
```

<Tip>
  Do the conversion once, at the boundary where a human-entered string becomes
  a number, and keep minor units everywhere inside your system. Converting
  back and forth in the middle of a flow is where rounding creeps in.
</Tip>

## Never use floats

Amounts are integers. The gateway's own money math is string-based and
refuses to do floating-point arithmetic on amounts, because `0.1 + 0.2` in a
settlement path reconciles to a discrepancy nobody can explain a month later.

```typescript theme={null}
// Wrong — introduces a representation error.
const fee = amount_minor * 0.015;

// Right — integer arithmetic, explicit rounding decision.
const fee = Math.round((amount_minor * 15) / 1000);
```

## Rules the gateway enforces

* **More precision than the currency allows is rejected**, not silently
  rounded. Sending `"1234.567"` USD fails rather than becoming `123456`.
* **Unknown currencies throw.** There is no default exponent.
* **Corridor minimums and maximums** apply per country, currency and method,
  and they fail closed. Query them at
  [`GET /v1/reference/corridors`](/api-reference/reference/the-full-country-x-currency-x-direction-x-method-matrix)
  rather than hardcoding.
* Some corridors have a **per-method floor** — for example Uganda bank payouts
  have a UGX 10,000 minimum, while mobile money has none.

## Fees

Collection fees are charged **per provider**, and the `amount_minor` you send
is always the **net** amount you expect to receive. Depending on the routed
provider, the fee is either appended on top of what the customer pays or
deducted from the gross. You do not need to model this difference — send the
net amount you want, and read the actual figures off the transaction once it
settles.

Payouts reserve the amount **plus an estimated fee** against your float, and
true up to the real fee when the payout settles. This is why a payout can be
refused with `insufficient_tenant_float` even when your balance looks like it
exactly covers the amount.
