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

# Open an account

> Apply for a brokerage account for a customer, follow the application until the broker opens the account, then fund it.

You open a brokerage account for a customer by sending an application. We pass it to the broker, which reviews it and opens the account. When it opens, you receive the account code, and you can fund it and trade for it.

## Who can have an account

An account is for one person who lives in Nigeria and is Nigerian. We send every application to the broker as an individual's, with Nigeria as the country of residence and the nationality: the only value the broker publishes for each. The API doesn't open joint accounts or accounts for companies.

The broker specifies no minimum age and decides each application: a refusal comes back as one of the [application reject reasons](/errors#application_reject_reasons), or as `uncertain` if the broker gives a reason it doesn't publish.

## Your customer's identity

Know your customer (KYC) checks are yours: you check your customer's identity before you apply, as your own obligations require. The API takes no documents, only the fields of the application. The broker checks the BVN, and refuses one it can't accept with `invalid_field`.

## 1. Look up the codes

Four fields take a code from the broker's lists, not free text:

| Field                                                    | Codes from                                                                  |
| -------------------------------------------------------- | --------------------------------------------------------------------------- |
| `state` (where the customer lives) and `state_of_origin` | `GET /v1/reference/states`                                                  |
| `lga_of_origin`                                          | `GET /v1/reference/states/{state}/lgas`, for the customer's state of origin |
| `bank` (the customer's own bank)                         | `GET /v1/reference/banks`                                                   |

## 2. Send the application

Send `POST /v1/applications` with a new [`Idempotency-Key`](/idempotency). In the sandbox, change `bvn` and `email` first: see [In the sandbox](#in-the-sandbox).

<CodeGroup>
  ```http Request theme={"dark"}
  POST /v1/applications
  Authorization: Bearer $KEY
  Content-Type: application/json
  Idempotency-Key: app-0001

  {
    "first_name": "Ada",
    "last_name": "Example",
    "gender": "F",
    "date_of_birth": "1990-01-15",
    "email": "ada@example.com",
    "phone": "+2348000000000",
    "street": "1 Example Street",
    "city": "Ikeja",
    "state": "LA",
    "postcode": "100001",
    "state_of_origin": "LA",
    "lga_of_origin": "LA-IKJ",
    "next_of_kin_name": "Ben Example",
    "next_of_kin_phone": "+2348000000001",
    "bank": "058",
    "bank_account_name": "Ada Example",
    "bank_account_number": "0000000000",
    "bank_account_opened": "2015-06-01",
    "bvn": "12345678901"
  }
  ```

  ```bash cURL theme={"dark"}
  curl -X POST https://$HOST/v1/applications \
    -H "Authorization: Bearer $KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: app-0001" \
    --data-binary @- <<EOF
  {
    "first_name": "Ada",
    "last_name": "Example",
    "gender": "F",
    "date_of_birth": "1990-01-15",
    "email": "ada@example.com",
    "phone": "+2348000000000",
    "street": "1 Example Street",
    "city": "Ikeja",
    "state": "LA",
    "postcode": "100001",
    "state_of_origin": "LA",
    "lga_of_origin": "LA-IKJ",
    "next_of_kin_name": "Ben Example",
    "next_of_kin_phone": "+2348000000001",
    "bank": "058",
    "bank_account_name": "Ada Example",
    "bank_account_number": "0000000000",
    "bank_account_opened": "2015-06-01",
    "bvn": "12345678901"
  }
  EOF
  ```
</CodeGroup>

```http Response theme={"dark"}
HTTP/1.1 201 Created

{
  "object": "application",
  "id": "app_3rbw6yq2kd5mz7tcxf4n",
  "state": "submitted",
  "reference": "KYC1790315920417",
  "account": null,
  "reason": null,
  "reject_reason": null,
  "param": null,
  "created_at": "2026-09-25T05:58:40Z",
  "updated_at": "2026-09-25T05:58:40Z",
  "client_reference": null
}
```

Every field's rules are in the [API reference](/api-reference/applications/apply-for-an-account). Before we send anything, we check the body and the form of each field:

| What's wrong                                                                 | Response                                                             |
| ---------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| A required field is missing.                                                 | `400 missing_required_field`, with `param` naming the field.         |
| A field breaks its rule, such as a date of birth in the future.              | `400 invalid_request`, with `param` naming the field.                |
| A field has the wrong JSON type, isn't a recognised field, or appears twice. | `400 invalid_request`, with no `param`. The message names the field. |

Then the broker checks the application. It publishes these formats:

| Field                                                                      | Format the broker publishes                                                                       |
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `gender`                                                                   | `M` or `F`.                                                                                       |
| `date_of_birth`, `bank_account_opened`                                     | `yyyy-MM-dd`.                                                                                     |
| `state`, `state_of_origin`, `lga_of_origin`, `bank`                        | A code from its lists.                                                                            |
| `email`, `bank_account_number`, `bvn`, `nin`                               | None, though it can refuse one as invalid.                                                        |
| `mothers_maiden_name`                                                      | None. Its request marks the field optional, yet one of its refusals is for a missing maiden name. |
| `next_of_kin_phone`, names and addresses                                   | None. It refuses one only when it's missing.                                                      |
| `phone`, `postcode`                                                        | None, and it publishes no refusal for them.                                                       |
| `middle_name`, `next_of_kin_relationship`, `next_of_kin_address`, `tax_id` | None, and it publishes no refusal for them. We send each one only if you do.                      |

Where the broker publishes no format, we send the field as you write it, trimmed of spaces at each end, so check it before you send, `phone` included. [The sandbox](#in-the-sandbox) takes any phone number. Send `mothers_maiden_name` whenever you have it.

When the broker refuses one field, you get `422 application_rejected` with `reject_reason` `invalid_field` and `param` naming the field. When it refuses the application as a whole, `reject_reason` is `rejected_by_broker` and there's no `param`. An error never repeats a field's value.

If the broker refuses an application with a reason it doesn't publish, we record it as `uncertain`, and our operations team reconciles it with the broker. An uncertain application the broker does not hold ends `unplaced`: [contact us](/support) with its `id` before you send it again.

## 3. Read the answer

| Response                   | Recorded as | What to do                                                                                                                                                                                                        |
| -------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `201`                      | `submitted` | Nothing. The broker has it, and `reference` is its reference. Wait for the account to open.                                                                                                                       |
| `201`                      | `uncertain` | Wait. The broker did not confirm receipt, and it is being reconciled with the broker. Don't send it again under a new key: if the broker has the first, it refuses the second as a duplicate.                     |
| `422 application_rejected` | `rejected`  | Read `reject_reason` and `param`. For `invalid_field`, correct that field and send the application with a new key. For the other reasons, see [Application reject reasons](/errors#application_reject_reasons).   |
| `503 application_not_sent` | `refused`   | It never reached the broker. Send it again with a new key.                                                                                                                                                        |
| `503 outcome_not_recorded` | `sending`   | Its outcome was not recorded. Retrieve the application the error's `application` names until it resolves. Don't send it again under a new key: if the broker has the first, it refuses the second as a duplicate. |

Every other refusal, and whether each one stored your key, is in [Idempotency](/idempotency#when-to-use-a-new-key).

If a corrected application comes back `already_at_broker`, don't send it again: [contact us](/support) with the `id` of each application. The broker doesn't specify whether it keeps an application it refused. The sandbox's broker doesn't, so there a corrected application with the same BVN is accepted.

## 4. Wait for the account to open

The broker decides how long its review takes. While we can reach the broker, we check on submitted applications about every 30 seconds. Each check asks about up to 20 applications, across all partners, the ones asked about longest ago first. We keep asking about each application for 30 days after you send it.

When the broker opens the account, we record the application as `opened`, with the account code in `account`, and you receive an `application.opened` [event](/webhooks) carrying it:

```json Opened application theme={"dark"}
{
  "object": "application",
  "id": "app_3rbw6yq2kd5mz7tcxf4n",
  "state": "opened",
  "reference": "KYC1790315920417",
  "account": "1000000001",
  "reason": null,
  "reject_reason": null,
  "param": null,
  "created_at": "2026-09-25T05:58:40Z",
  "updated_at": "2026-09-25T06:04:10Z",
  "client_reference": null
}
```

The account also appears in `GET /v1/accounts`, with `application` set to the application's `id`.

From the broker's answer about an application we read only its account number: the broker doesn't publish the statuses its answer can carry, so a decline cannot be read from it. When the broker declines an application, our operations team records it as `rejected`, with `reject_reason` `declined_by_broker`.

If an application is still `submitted` 30 days after you sent it, we stop asking. [Contact us](/support) with its `id`.

## Application states

```mermaid theme={"dark"}
%%{init: {"theme": "neutral"}}%%
stateDiagram-v2
    [*] --> sending
    sending --> submitted
    sending --> uncertain
    sending --> rejected
    sending --> refused
    sending --> unplaced
    uncertain --> submitted
    uncertain --> unplaced
    submitted --> opened
    submitted --> rejected
    opened --> [*]
    rejected --> [*]
    refused --> [*]
    unplaced --> [*]
```

| State       | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Who moves it there                                                                      | Final |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ----- |
| `sending`   | We've stored it and are sending it. You normally see it only while your request is in progress. If it still reads `sending` after its request has ended, its outcome was not recorded, and our check of submitted applications doesn't ask about it: our operations team reconciles it with the broker. It becomes `submitted` or `unplaced`, the same business day, or the next if its request ended outside 9:00 to 17:00 West Africa Time (WAT), Monday to Friday. | Your request                                                                            | No    |
| `submitted` | The broker has it.                                                                                                                                                                                                                                                                                                                                                                                                                                                    | Your request, or our operations team reconciling an uncertain one or one left `sending` | No    |
| `uncertain` | The broker did not confirm receipt. Our operations team reconciles it.                                                                                                                                                                                                                                                                                                                                                                                                | Your request                                                                            | No    |
| `opened`    | The broker opened the account. `account` holds its code.                                                                                                                                                                                                                                                                                                                                                                                                              | Our check of submitted applications                                                     | Yes   |
| `rejected`  | The broker refused it. `reject_reason` says why, and `param` names the field at fault, if one is.                                                                                                                                                                                                                                                                                                                                                                     | Your request, or our operations team on the broker's word                               | Yes   |
| `refused`   | It never reached the broker.                                                                                                                                                                                                                                                                                                                                                                                                                                          | Your request                                                                            | Yes   |
| `unplaced`  | Our operations team confirmed the broker does not hold it: it never received it, or refused it with a reason it doesn't publish.                                                                                                                                                                                                                                                                                                                                      | Our operations team, reconciling an uncertain one or one left `sending`                 | Yes   |

Each move to a state after `sending` sends an `application.<state>` [event](/webhooks).

## A customer the broker already knows

The broker refuses an application when its BVN or its email address already belongs to one of the broker's customers, or when its BVN matches an application the broker already holds. All three come back as `422 application_rejected` with `reject_reason` `already_at_broker`. The answer tells you the broker already knows that BVN or email address. It doesn't say which, or whether the person is a customer or an applicant.

Don't send the same application again. If your customer already has an account with the broker, we can link it to you. [Contact us](/support) with the application's `id`, and we tell you how to send the customer's written consent. Once the broker confirms the account is theirs, we link it to you, and you use its account code as you would any other.

## What the API doesn't do yet

Once an account is open, the API can't yet change a customer's details, close the account, or move its shares to another broker. For any of these, [send us](/support#what-to-send-for-each-request) the account code and what you need, without the customer's personal details, and we take it to the broker.

## In the sandbox

A submitted application opens automatically at our next check, so you can follow it to `opened` and receive the event. One test value makes the broker reject an application at once instead: see [Test values](/sandbox#test-values). In the sandbox you can send that application again. In live, [contact us](/support) before you send a `rejected_by_broker` application again.

The sandbox's broker also refuses an `email` without an `@`, and a code that isn't in its lists, so you can rehearse `invalid_field` and a corrected resend. It takes any phone number, postcode, name or bank account number.

The sandbox's broker keeps the BVN and email address of every application it accepts, from every partner, until the sandbox restarts. It refuses a second application with either as `422 application_rejected` with `reject_reason` `already_at_broker`. Before you send the example above, change its `bvn` to another valid BVN that isn't all zeros (a [test value](/sandbox#test-values)), and its `email` to an address of your own. The [Postman collection](/matamba-gateway.postman_collection.json) does this on each run.

## Then fund the account

Once the account is open, allocate money to it from your wallet. See [Fund an account](/funding).
