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

# Get started with accounts

> Open an NGN account, give it a payable destination, and confirm it can reach the outside world.

This page opens one business account in sandbox and issues its funding destination. It does not move money.

## Before you start

```bash theme={null}
export SAKAWAVE_API_KEY='sk_test_replace_with_your_key'
export SAKAWAVE_BASE_URL='https://sandbox-api.sakawave.example'
```

Start with NGN. Any fiat currency opens, but issuing details for any other currency needs approved verification and international access, and moving money in it needs tier 3. Those failures are `403`s rather than anything you can fix in code.

## 1. Open the account

Three fields are required: `name`, `type`, and `currency`.

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url "$SAKAWAVE_BASE_URL/v1/accounts" \
    --header "Authorization: Bearer $SAKAWAVE_API_KEY" \
    --header "Content-Type: application/json" \
    --data '{
      "name": "Operating NGN",
      "type": "operational",
      "currency": "NGN",
      "metadata": { "ledger": "main" }
    }'
  ```

  ```ts Node.js theme={null}
  const response = await fetch(`${process.env.SAKAWAVE_BASE_URL}/v1/accounts`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.SAKAWAVE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      name: 'Operating NGN',
      type: 'operational',
      currency: 'NGN',
      metadata: { ledger: 'main' },
    }),
  });

  const account = await response.json();
  if (!response.ok) {
    throw new Error(`${response.status}: ${account.code ?? account.reason} — ${account.message}`);
  }

  console.log(account.id, account.tag, account.transferReady);
  ```

  ```python Python theme={null}
  import os
  import requests

  response = requests.post(
      f"{os.environ['SAKAWAVE_BASE_URL']}/v1/accounts",
      json={
          "name": "Operating NGN",
          "type": "operational",
          "currency": "NGN",
          "metadata": {"ledger": "main"},
      },
      headers={"Authorization": f"Bearer {os.environ['SAKAWAVE_API_KEY']}"},
      timeout=30,
  )
  account = response.json()
  if not response.ok:
      raise RuntimeError(
          f"{response.status_code}: {account.get('code') or account.get('reason')} — {account['message']}"
      )

  print(account["id"], account["tag"], account["transferReady"])
  ```
</CodeGroup>

Note the error handling: it reads `code` **or** `reason`. Tier denials report `reason` and carry no `code` at all, which is the one place the error contract varies.

A `201` returns the account:

```json theme={null}
{
  "id": "01K2Z3AB4D5E6F7G8H9JKMNPQR",
  "name": "Operating NGN",
  "type": "operational",
  "category": "fiat",
  "status": "active",
  "currency": "NGN",
  "balance": "0",
  "balanceFormatted": "0.00",
  "tag": "AA0271849366",
  "canDebit": true,
  "canCredit": true,
  "isWalletAccount": false,
  "transferReady": false,
  "customerId": null,
  "metadata": { "ledger": "main" },
  "closedAt": null,
  "closureReason": null,
  "createdAt": "2026-08-14T09:02:00.000Z",
  "updatedAt": "2026-08-14T09:02:00.000Z"
}
```

Three things to notice:

1. **`transferReady` is `false`.** The account exists and is active, but nothing outside Sakawave can pay it yet. That is the next step.
2. **`tag` was minted for you.** Two letters and ten digits, the last a check digit. It is public and unique across Sakawave: another business can pay this account by tag alone.
3. **`balance` is the string `"0"`, in minor units.** Never do arithmetic on `balanceFormatted`.

## 2. Issue a funding destination

```bash theme={null}
curl --request POST \
  --url "$SAKAWAVE_BASE_URL/v1/accounts/$ACCOUNT_ID/details" \
  --header "Authorization: Bearer $SAKAWAVE_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{}'
```

The body is empty for a fiat account — the account already determines everything. For a crypto account, name the chain: `{"chain": "BASE_MAINNET"}`.

**`201`** returns the destination:

```json theme={null}
{
  "account": { "id": "01K2Z3AB4D5E6F7G8H9JKMNPQR", "currency": "NGN", "name": "Collections" },
  "beneficiary": { "name": "Ada Okafor", "address": null },
  "details": {
    "id": "01K2Z3AB4P5Q6R7S8TVWXYZABC",
    "type": "bank_account",
    "domestic": {
      "accountNumber": "0123456789",
      "routingNumber": null,
      "sortCode": null,
      "bankName": "Example Bank",
      "bankAddress": null
    },
    "international": null,
    "wallet": null,
    "paymentRails": [],
    "providerBacked": true,
    "createdAt": "2026-08-14T09:03:00.000Z"
  }
}
```

**`202`** means the provider accepted the request and is still working. There is no body. Poll `GET /v1/accounts/{accountId}/details` until the detail appears, or wait for the `account.detail.created` webhook.

```ts theme={null}
if (response.status === 202) {
  // Nothing to read yet. The destination will arrive; do not re-issue on a timer.
  return { pending: true };
}
```

Re-issuing is safe — provisioning resumes rather than duplicating — but a tight retry loop just adds load. Wait for the event.

## 3. Confirm it is payable

```bash theme={null}
curl --request GET \
  --url "$SAKAWAVE_BASE_URL/v1/accounts/$ACCOUNT_ID" \
  --header "Authorization: Bearer $SAKAWAVE_API_KEY"
```

`GET` on a single account is the only read that includes `details`. You are looking for two things: `transferReady` is now `true`, and `details` has at least one entry. Publish that entry's `accountNumber` to whoever will be funding the account.

## If it failed

| Status | `code` / `reason` | What to do |
| - | - | - |
| `400` | `validation_failed` | Check `type` — only `operational` and `savings` can be opened. |
| `400` | `chain_required` | A crypto account needs a `chain`. |
| `400` | `chain_not_applicable` | A fiat account must not be given a `chain`. |
| `403` | `kyc_approval_required` | You issued details on a non-NGN account before your verification was approved. Finish your verification. |
| `403` | `international_questionnaire_required` | You issued details on a non-NGN account before applying for international access. Complete the one-time questionnaire in the dashboard. |
| `403` | `international_account_approval_required` | You issued details on a non-NGN account without an approval for international access. Start with NGN until your application is approved. |
| `403` | `international_customer_wallet_not_supported` | You supplied `customerId` with a non-NGN currency. Customer wallets are NGN only. |
| `403` | `reason: feature_not_available` | You opened a crypto account below tier 2. Read `requiredTier`. |
| `409` | `currency_capability_unavailable` | Sakawave has issuance switched off for that currency. |
| `409` | `account_not_active` | You tried to issue a detail on an account that is not active. |
| `503` | `provider_unavailable` | The provider was unreachable. Nothing was created; retry shortly. |

## Next

* [Currencies and funding rails](/services/accounts/currencies-and-funding-rails) — what you may hold, and how it is funded
* [Use cases](/services/accounts/use-cases) — customer wallets, reconciliation, and the `202` path
* [Make a transfer](/tutorials/make-a-transfer)


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