> ## 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.

# Capabilities and corridors

> The six currency switches, which one is checked where, and how routes and FX corridors decide whether money can actually move.

This is the currencies-specific integration guide. It answers one question in detail: given a currency, what is actually checked before an operation is allowed, and which failure tells you which thing was off.

## The six switches

Every currency carries six independent booleans. They are not a hierarchy — a currency can be transferable but not convertible, or convertible but closed for new accounts.

| Switch | Gates |
| - | - |
| `accountIssuanceEnabled` | Opening an account in this currency |
| `transferEnabled` | Being the **source** of a transfer |
| `ownTransferEnabled` | Being the **destination**, paying your own account |
| `tagTransferEnabled` | Being the **destination**, paying a Sakawave tag |
| `externalTransferEnabled` | Being the **destination**, paying an external destination |
| `fxEnabled` | Being either side of a conversion |

## Which check fires where

Read this table before designing an error-handling strategy — it tells you how many things must be true for one call to succeed.

| Operation | Checks, in order |
| - | - |
| Open an account | `accountIssuanceEnabled` on the account's currency |
| Transfer to your own account | `transferEnabled` on source → `ownTransferEnabled` on destination |
| Transfer to a Sakawave tag | `transferEnabled` on source → `tagTransferEnabled` on destination |
| Payout to an external destination | `transferEnabled` on source → `externalTransferEnabled` on destination |
| Any of the above, converting | …plus `fxEnabled` on **both** → plus an active FX corridor for the pair |

A same-currency payout therefore reads two switches. A cross-currency one reads four and then a corridor.

Every one of these failures is the same status and the same code:

```json theme={null}
{
  "statusCode": 409,
  "message": "USD is not available for this operation.",
  "code": "currency_capability_unavailable"
}
```

The message names the currency but not the capability, so when you handle this, log which operation you were attempting — otherwise you cannot tell a closed source from a closed destination.

## FX corridors

Conversion needs one more thing that no endpoint exposes: an **active corridor** for the ordered pair of currencies. `fxEnabled` on both sides is necessary and not sufficient.

```json theme={null}
{
  "statusCode": 409,
  "message": "This currency conversion is not available.",
  "code": "fx_corridor_unavailable"
}
```

Corridors are directional. NGN → USD being wired does not imply USD → NGN.

<Warning>
  There is no endpoint that lists FX corridors. You cannot precheck a specific pair, only that both currencies permit conversion at all. Handle `fx_corridor_unavailable` at the point of use, however thorough your precheck.
</Warning>

## Routes versus corridors

Two endpoints answer "what can deliver to this currency", and they are not interchangeable.

| | `GET /v1/currencies/{code}/routes` | `GET /v1/transfer-corridors` |
| - | - | - |
| Shape | The route row as stored | A resolved, provider-neutral view |
| Currency data | Code only | Code, name, symbol, `minorUnits` |
| Countries | `destinationCountry`, or null for multi-country schemes | `countries[]`, expanded from the scheme |
| Filters | `country` | `currency`, `country`, `paymentMethod` |
| Internal fields | Returns `provider` and `serviceCode` | Withholds them |
| Rate limit | 600/min | 120/min |

**Use corridors to build anything a person looks at.** The expanded country list and resolved currency data are exactly what a picker needs, and the provider-neutral shape will not change under you.

**Use routes when you already know the currency** and want `routeId` values with the least ceremony — a background job resolving a stored preference, say.

<Note>
  `provider` and `serviceCode` on the routes endpoint are internal routing detail. Corridors withhold them on purpose, and the accounts API refuses to expose the equivalent. Treat their presence here as an accident of implementation, not a contract.
</Note>

## Multi-country schemes

A route's country coverage comes from one of two places, and the difference matters when you filter.

* **`destinationCountry` is set.** The route serves that one country.
* **`destinationCountry` is null.** Coverage comes from the `scheme` — `sepa` covers the SEPA area, `cemac` covers the CEMAC members — or, for mobile money, from the destination currency's home country.

Passing `?country=DE` to a currency's routes keeps SEPA routes even though their `destinationCountry` is null, because the filter understands the scheme. Do not implement that logic yourself against `destinationCountry`; you will drop every multi-country route.

## Minor units, per currency

`minorUnits` belongs to the currency, not to your code.

| Currency | `minorUnits` | ₦25,000 / \$250 / USh 3,500 becomes |
| - | -: | - |
| NGN, USD, GBP, EUR, GHS, ZAR, CAD, MXN, AED | 2 | `"2500000"`, `"25000"` |
| UGX | **0** | `"3500"` |
| BTC, ETH, USDT, USDC | 8 | `"42000"` for 0.00042 |

Every amount in the API is an integer of these units carried as a string. A single hardcoded `/ 100` is wrong for UGX and wrong for every crypto asset — read `minorUnits` and store it beside the amount.

## A precheck worth writing

```ts theme={null}
async function explainWhyNot(sourceCode, destinationCode, destinationType) {
  const currencies = await cachedCurrencies();               // GET /v1/currencies
  const source = currencies.find((c) => c.code === sourceCode);
  const destination = currencies.find((c) => c.code === destinationCode);

  if (!source) return `${sourceCode} is not an active currency`;
  if (!destination) return `${destinationCode} is not an active currency`;
  if (!source.transferEnabled) return `${sourceCode} cannot be a transfer source`;

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

  if (sourceCode !== destinationCode && !(source.fxEnabled && destination.fxEnabled)) {
    return 'conversion is not available for one of these currencies';
  }

  return null;   // As far as reference data can tell. The corridor may still refuse.
}
```

That final comment is the whole point of this page: reference data gets you most of the way, and the last step belongs to the error handler.


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