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

# Accounts setup and configuration

> What is checked when an account opens, when its details are issued, and when money moves; rate limits, webhook wiring, and what has to be true before your first call.

## Credentials

API key only; session credentials are rejected.

```http theme={null}
Authorization: Bearer sk_test_your_key_here
```

Accounts are scoped to the key's business. No combination of query parameters reaches another business's rows — tenancy is bound in the query layer, not in the filter grammar.

## What is checked, and when

A fiat account is a local balance until its details are issued, so the checks are split across three points: opening the account, issuing its details, and moving money through it. Each check has its own status and its own failure shape, and knowing which one you hit is the difference between a configuration change and a support conversation.

### Opening an account

`POST /v1/accounts` runs three checks in order.

<Steps>
  <Step title="Is the currency open for issuance?">
    Sakawave can switch account issuance off per currency. Closed answers `409 currency_capability_unavailable`. Nothing you configure changes this.
  </Step>

  <Step title="Is this a crypto account below tier 2?">
    Crypto requires tier 2 for the deposit feature. A denial answers `403` carrying `reason`, `currentTier`, and `requiredTier` — and **no `code`**.
  </Step>

  <Step title="Is this a customer wallet in a non-NGN currency?">
    Refused outright with `403 international_customer_wallet_not_supported`, regardless of your own approvals.
  </Step>
</Steps>

Your own fiat account is not checked against your tier, your verification, or your international access. It opens in any supported currency, at any tier.

### Issuing details

`POST /v1/accounts/{accountId}/details` is the first call to a provider on an account's behalf. For your own non-NGN fiat account, it checks your verification first and your international access second.

| `code` | Meaning | What to do |
| - | - | - |
| `kyc_approval_required` | Your verification is not approved. | Finish your verification. |
| `international_questionnaire_required` | You have not applied for international access, or your last application was withdrawn or expired. | Complete the one-time questionnaire in the dashboard. |
| `international_account_approval_required` | You have applied for international access and do not hold an active approval. | Nothing in code. The application needs a decision. |

All three answer `403` and carry the account's `currency`. International access is one approval for your business, not one per currency. Both checks run on every request, so an approval withdrawn after an account opened stops new details being issued for it.

NGN and crypto accounts skip both checks.

### Moving money

Sending money from an account, and crediting a deposit that arrives through its details, are checked against the holder's tier for the account's currency. NGN moves at tier 1; every other fiat currency moves at tier 3. Below that, a transfer is refused with `403` carrying `reason: currency_not_available`, `currentTier`, and `requiredTier`, and a deposit through the account's details is not credited.

<Warning>
  Tier denials are the one error family that reports `reason` instead of `code`. Error handling that reads only `code` will log `undefined` for exactly the failures that need a human decision. Read `code ?? reason`.
</Warning>

### What this means in practice

| You want | Works today |
| - | - |
| Your own NGN account | Yes |
| Your own USD, GBP, EUR, GHS, ZAR, UGX, CAD, MXN, or AED account | Opens at any tier. Details need approved verification and international access. Money moves only at tier 3 |
| Your own crypto account (BTC, ETH, USDT, USDC) | Only at tier 2 or above |
| A customer's NGN wallet | Yes |
| A customer's wallet in any other currency | **No** — non-NGN customer wallets are refused outright |
| A customer's crypto wallet | **No** — crypto needs tier 2; customers are tier 1 |

## Rate limits

Per 60-second window, counted twice — once against your business, once against the key.

| Operation | Requests per minute |
| - | -: |
| Reads (`GET`) | 600 |
| `POST /v1/accounts`, `POST /v1/accounts/{accountId}/details` | 120 |

Plus 300 per minute per IP and 1,200 per minute per credential across all routes. See [rate limits](/guides/rate-limits).

## Webhooks

| Event | Trigger | Why you care |
| - | - | - |
| `account.created` | An account was opened | Mirror it locally. |
| `account.updated` | Bound to a provider, closed, or changed by the provider | **This is how you learn `transferReady` became true.** |
| `account.detail.created` | A destination was issued | The payload of a `202` you are waiting on. |
| `account.detail.updated` | A provider changed an existing destination | Re-publish if you display the number. |

Both `account.*` payloads carry the same object the REST endpoints return; `account.detail.*` carry the detail. Match a detail to your account by `account.id`: after a `202` you have never seen the detail's own `details.id`.

Deduplicate on the `Sakawave-Webhook-Id` header — the body has only `event` and `data`. See [verify signatures](/webhooks/verify-signatures).

<Note>
  Internal platform balances, such as card settlement accounts, emit nothing and never appear in these APIs. If you see an `account.*` event, it is for an account you can also read over REST.
</Note>

## Provider dependence

Opening an account is local and always fast. **Issuing a detail is not** — it talks to a payment provider, and that call can fail (`503 provider_unavailable`) or not finish in time (`202`).

Design for both:

* Treat `503` as retryable. Issuance walks three steps at the provider and skips any already taken, so a retry resumes rather than duplicating.
* Treat `202` as normal, not exceptional. Do not block a signup flow on a destination that has not arrived.

## Configuration checklist

* [ ] Error handling reads `code ?? reason`, and logs `currentTier` and `requiredTier` when present
* [ ] `transferReady` is checked before advertising an account as payable
* [ ] `balance` used for arithmetic; `balanceFormatted` only for display
* [ ] `202` handled as a normal outcome of issuing a detail
* [ ] `account.updated` and `account.detail.created` subscribed, rather than polling
* [ ] Non-NGN plans confirmed against your verification and international access before you write the code
* [ ] Offboarding flow accounts for closure being dashboard-only


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