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

# Currencies and countries use cases

> Four flows — building pickers, driving a payout form from field descriptors, prechecking a transfer, and keeping a cache honest.

## 1. Build the pickers your other calls depend on

Three pickers, three calls, all cacheable.

```bash theme={null}
# Currencies you can open an account in
curl --silent --url "$SAKAWAVE_BASE_URL/v1/currencies?capability=account_issuance" \
  --header "Authorization: Bearer $SAKAWAVE_API_KEY" \
| jq '[.items? // .[] | {code, name, symbol, minorUnits}]'

# Countries you can onboard someone in — no key needed
curl --silent --url "$SAKAWAVE_BASE_URL/countries" | jq '.countries[] | {code, name, dialCode}'
```

Populate `jurisdiction` and `address.country` on a customer from the country list, and the currency field on an account from the capability-filtered currency list. Both refusals — an unserviceable jurisdiction, a currency closed for issuance — then become impossible to reach from your UI.

<Note>
  The country list is what Sakawave will **onboard**. It is not the list of places you can **pay**. For payouts, ask for routes.
</Note>

## 2. Drive a payout form from field descriptors

This is the single most valuable thing in the service. A route tells you exactly what to collect, so you never have to hardcode a country's banking fields.

```ts theme={null}
const [corridor] = await get(`/v1/transfer-corridors?currency=KES&country=KE`);

// fieldDescriptors is a form specification, not a hint.
const fields = corridor.route.fieldDescriptors.map((descriptor) => ({
  name: descriptor.key,
  label: descriptor.label,
  kind: descriptor.type,              // 'text' | 'select' | 'tel'
  required: descriptor.required,
  minLength: descriptor.minLength,
  maxLength: descriptor.maxLength,
  pattern: descriptor.pattern,
  placeholder: descriptor.placeholder,
  options: descriptor.options,        // present for 'select'
}));
```

Send what you collected straight through as `fields` when creating a payment detail, alongside the route's `id`:

```json theme={null}
{
  "routeId": "01K2Z3AB4M5N6P7Q8RSTVWXYZA",
  "currency": "KES",
  "country": "KE",
  "accountHolderName": "Ada Okafor",
  "fields": { "bankCode": "001", "accountNumber": "0123456789" }
}
```

Two details worth honouring:

* **`institutionDirectoryEnabled`** means Sakawave resolves the institution code to a canonical name. Submit the code from `options`; do not ask the user to type a bank name.
* **`minimumAmount` and `maximumAmount`** are in minor units and are per route. Validate against the route the user actually selected, not against a global constant.

## 3. Precheck before you attempt

A cross-currency payout reads four switches and a corridor. Checking them yourself turns a mid-flow `409` into a disabled button.

```ts theme={null}
function canSend(source, destination, destinationType) {
  if (!source.transferEnabled) return 'source currency cannot send';

  const destinationSwitch = {
    own_account: 'ownTransferEnabled',
    sakawave_tag: 'tagTransferEnabled',
    external: 'externalTransferEnabled',
  }[destinationType];
  if (!destination[destinationSwitch]) return `destination cannot receive by ${destinationType}`;

  if (source.code !== destination.code) {
    if (!source.fxEnabled || !destination.fxEnabled) return 'conversion unavailable';
    // The pair also needs an active FX corridor, which this service does not
    // expose. That one only surfaces as 409 fx_corridor_unavailable.
  }
  return null;
}
```

The last comment is the honest limit of prechecking: **FX corridors are not listed anywhere in this API**. You can confirm both currencies allow conversion, but not that the specific pair is wired. Handle `409 fx_corridor_unavailable` regardless of what your precheck said.

## 4. Keep the cache honest

Reference data emits no events, so a stale cache is silent until it causes a failure. Two mechanisms, both cheap:

**Scheduled refresh.** Once an hour for currencies and routes, once a day for countries. Compare against what you held and log the difference — a route disappearing is worth knowing about before a customer meets it.

```ts theme={null}
const fresh = await get('/v1/currencies');
const withdrawn = cached.filter((was) => !fresh.some((is) => is.code === was.code));
const changed = fresh.filter((is) => {
  const was = cached.find((c) => c.code === is.code);
  return was && was.updatedAt !== is.updatedAt;
});
if (withdrawn.length || changed.length) await alertOperations({ withdrawn, changed });
```

**Correct on error.** When a `409 currency_capability_unavailable` or `409 fx_corridor_unavailable` comes back, refresh before retrying anything. The error is the authority; the cache never is.

## What you cannot ask this service

* **Whether *you* may use a currency.** Capability is platform-wide. Your verification, international access, and tier are checked by [accounts](/services/accounts/setup-and-configuration#what-is-checked-and-when), and they fail with `403`, not `409`.
* **Which FX pairs are wired.** There is no corridor listing for conversion.
* **Which provider handles a route.** The corridors endpoint withholds it deliberately; the routes endpoint currently returns `provider` and `serviceCode`, but they are internal detail and may be withdrawn — do not branch on them.
* **Live rates.** Quotes come from [FX quotes](/api-reference/fx-quotes/create-an-fx-quote).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.