Skip to main content
Requirements are what an account provides before a capability goes live. Each capability requires certain fields, and the capability stays off until those fields are provided and pass their checks. They are computed on every read from what the account’s capabilities require against what has been accepted, so they change when you request a capability or when a document is rejected. They resolve by country and entity type: changing either recomputes the whole set.

What an account owes

An account owes only what the capabilities you ask for need. Ask for less, and the person behind it gives less. Each row below adds to the rows above it. A person (entity_type: "individual"): We never ask a person for a website, product category or support email. A company (entity_type: "company"): Owners we ask about, by country. Directors are always asked. Owners are asked when they own at least this share, or more than it where it says “over”. In Cameroon, a person who only gets paid out also gives their date of birth, phone and ID number. The list for mobile_money has not changed. Read the account’s requirements for what it asks.
Name every capability you need when you create the account. If you name none, the account is asked for everything its configuration allows.

Where to read them

They live on the account object. GET /v1/accounts/{account_id} returns the requirements block on every read. All take scope connected_accounts:read. entries[] is the one to build a form from when an account holds more than one capability: a flat bucket cannot say that one field blocks payouts while another blocks nothing the account has asked for.

Field states

Each entry in requirements.entries[] carries a status, naming the bucket it is listed under. Only outstanding fields become entries. A field that has been provided and accepted is not listed at all, so an empty entries means nothing is outstanding. A field awaiting a reviewer is reported as pending_verification rather than a status of its own: to a platform, both mean the same thing, which is that the field is with us and re-sending it achieves nothing. Each entry has these keys: The field key names what the entry is about. company.* and business_profile.* are the account itself, persons.per_3a91c0d7.* a specific person. Read that person at /persons/{person_id}. A field becomes required as eventually_due or currently_due, moves to past_due when its deadline passes, to pending_verification when submitted, then leaves the list once accepted or returns to currently_due if rejected or if re-verification is required. A field becomes required as eventually_due or currently_due, moves to past_due when its deadline passes, to pending_verification when submitted, then leaves the list once accepted or returns to currently_due if rejected or if re-verification is required.

What a field blocks

Each entry carries restricts_capabilities, the capabilities that stay off while it is outstanding. An empty array means no capability the account has asked for needs this field. Read it per entry rather than counting the buckets: an account holding two capabilities can have one fully satisfied while the other is blocked by a single field, which a flat list cannot express.
Nothing outstanding does not mean a capability is on. Gate on the capability’s status being active. See Capabilities.

Payouts abroad

Basic payouts asks for a name (a company’s legal name) and a payout account. That is enough to be paid out to a bank in Nigeria. When you turn on international payouts for an account, payouts also asks:
  • an individual for their email and address;
  • a company for its own address: company.address.line1, company.address.city and company.address.country.
They appear as currently_due entries with payouts in restricts_capabilities, and in the hosted onboarding checklist.
  • If payouts is not active yet, it waits for them before it goes live.
  • If payouts is already active, it stays active. Payouts in Nigeria carry on.
What blocks a payout abroad is the payout itself. When you quote it, when you create it, and again before the money is sent, we check for a name, an email and an address. Until they are on file, a payout abroad is refused with PAYOUT_RECIPIENT_INCOMPLETE, even if the requirements show nothing due. The account’s contact_email counts as its email. Accounts that had payouts before this option existed have it on already. Where they are asked for these details, the details are listed as eventually_due once payouts is live, and block nothing.

Who can act, and by when

Each entry carries a resolution. A field with a deadline carries it as deadline, and the account’s requirements.current_deadline is the soonest one across everything outstanding, enough to drive a banner without walking every entry. Both are null when no deadline has been set.

When an account is already live

If you ask for a new capability on a live account and it now owes more, the new fields show as eventually_due. They do not appear in restricts_capabilities, and nothing the account already has is switched off because of them.

The account roll-up

The account object carries a smaller block for a status badge:
These are field keys, not objects. errors carries a field that was provided and then rejected, as distinct from one that is missing.

Submitting

POST /v1/accounts/{account_id} · scope connected_accounts:write fields is keyed by the field keys the requirements name: persons, company.*, business_profile.*, payout_destination. A submission that omits a field leaves it untouched, so you can save an account holder’s progress as they fill in a form. But every field you do send is validated together, and if any one of them is invalid the submission is refused: none of the fields in that call are saved, not even the ones that were fine. There is no partial save of a rejected submission. Retry with only the corrected fields, or the whole fields object again once it is fixed. This applies to every field class in the payload, not only payout_destination: an invalid company.structure or a malformed entry in persons[] in the same call also refuses the whole submission. A field that is merely incomplete (one you have started but not finished, so a required sub-field is still absent) is not a rejection. The call succeeds and the requirement stays in currently_due until you send the rest. You still see it in errors[] with the code required, alongside any field that was actually rejected. The refusal covers the fields object, not the rest of the request. Contact details, capability requests, profile changes and an entity-type change sent in the same call are applied before the fields are validated, and a rejected submission does not undo them. A call that mixes them is not atomic: if the fields are refused, retry the fields; the rest already took effect. Requirement values ride on the account write, so the same call can set contact details and request capabilities. See Update an account. The response is the account, with its recomputed requirements block.
Bank codes and mobile money operators live under /v1/reference/, with no account in the path, because the NG bank list is the same for every account. Resolving an account number to its holder’s name is an operation rather than a lookup, so it sits at POST /v1/misc/bank-accounts/resolve. It allows 20 calls a minute, since each one reaches the banking network and answers about a real account. See Onboard through the API.

The persons shape

persons is an array. Each entry is one human, identified across calls by the persons.<person_id> prefix its requirements carry once created. Submit a person by including it in the array; omit a field to leave it untouched, so a form can save as it is filled.
Add a BVN (or any further identifier) as another entry in the same list. It sits beside the primary ID rather than replacing it, because a NIN identifies a citizen and a BVN a bank customer, and a reviewer may need both:
A person’s requirements (persons.<id>.name, .dob, .id_number, .nin, .bvn, .tax_id, .identity_check, …) come from the capabilities the account asks for. See What an account owes. The id_number requirement is satisfied by any entry in id_numbers that is not a BVN or a tax ID. Add a NIN, BVN or tax ID as another entry in the list. Fields that belong to the representative only count once the person has relationship.representative: true. persons.<id>.identity_check is the selfie video check. You cannot do it through the API: the person does it on their phone. Send them a hosted link for it. See Onboarding.

The profile shape

A person who takes payments (bank_transfer, ngn_card_collection, crypto, card_collection) tells us what they do and where their money comes from.

The business_profile shape

An account that takes card payments in USD (card_collection) shows us what it sells. Our card partner asks for both fields. No other capability asks for them.

Company fields

A registered business (entity_type: "company") also owes company.* fields: registration number, structure, and country-specific filings. company.structure is an enum whose valid values, and which documents each structure owes, depend on the country; read them from GET /v1/reference/business-structures. An individual account (entity_type: "individual") owes none of these.

Documents (id_document, company filings)

A document requirement (a person’s id_document, a company’s certificate of incorporation) is a file, not a value, so it is uploaded separately and then referenced. It cannot be sent inline in the fields object.
1

Upload the file

POST /v1/files (multipart), with the bytes and a purpose that says what the file is for:
  • identity_document: a person’s government ID or address evidence.
  • account_requirement: a company filing or supporting evidence.
Send X-Account-Id set to the connected account the file is for. A file belongs to the account it was uploaded for, so a file uploaded without the header belongs to your platform account and cannot be attached to a connected account. The attach step then returns 404 NOT_FOUND with Attachment not found.
The response returns the file id. JPEG, PNG and PDF are accepted, up to 10 MB. A PDF identity document is fine.
2

Attach it

Attach the uploaded file to the slot it satisfies, passing the file id from the previous step as file. A person’s identity document attaches to that person: POST /v1/accounts/{account_id}/persons/{person_id}/documents with the file id and the document slot. See Verify an account’s identity. A company filing attaches to the account: POST /v1/accounts/{account_id}/documents, naming the company document slot.
Documents cannot go through a hosted onboarding link’s automatic flow either: whether you onboard by API or by link, the file itself is uploaded through POST /v1/files.

The payout_destination shape

payout_destination is a tagged union: type picks the rail, and the rest of the object is whichever fields that rail needs. currency is required alongside them (a legacy submission that omits it is inferred from the account’s country instead). The currency and destination type must have a configured payout provider. Local bank destinations in NGN, GHS, KES, TZS and UGX and mobile money destinations in GHS, KES and TZS have configured routes. Payouts are available to all Bachs users; the account still needs to complete its requirements and have a usable destination. An account can be paid out to a mobile money wallet, or to a bank outside Nigeria, only when international payouts are on for it. This field still saves such a destination without it, but a payout to it is refused with INTERNATIONAL_PAYOUTS_NOT_ENABLED, and so is adding one through POST /v1/payouts/destinations. See Payout currencies and methods. For stablecoin destinations, use an asset code from the supported payout networks.
The inline payout_destination requirement field does not accept banks that need international routing schemes, such as GBP, EUR, USD and CAD destinations. Add those through POST /v1/payouts/destinations with the appropriate routing details. See the payout destination reference. This is a separate submission path; adding a destination does not replace the account’s other onboarding requirements or activate its capabilities.
A bank account’s bank_code and a mobile money submission’s mobile_provider are also checked against the account’s own country: a Ghanaian account cannot name a Nigerian bank, and vice versa. The bank account must be in the account’s own name. We ask the bank for the name on the account and compare it with the person’s name, or with a company’s registered legal name.
  • The names match, for example “ADA OKAFOR” for Ada Okafor: the destination is saved. Words like Limited, Ltd and Nig are ignored for companies.
  • Some words match, for example “Okafor Ventures” for Ada Okafor: the destination is saved, and our team checks it.
  • No words match: the destination is refused and nothing is saved. You get INVALID_REQUIREMENT_FIELD with code account_name_mismatch.
Only Nigerian banks return a name today, so other destinations are not compared. The check does not run in the sandbox.

When a submission is rejected

A rejected requirement-field write can return 400 with error_code: "INVALID_REQUIREMENT_FIELD" and an errors array. For example, a GBP bank destination is rejected through this inline field because it needs routing details supplied through the payout-destination API. Read the returned field and message before choosing another submission path. See Errors for the error envelope and Error Reference for every error_code. When a check fails. A failed selfie video check or BVN check is not a refused write. It shows in the account’s requirements.errors, on persons.<id>.identity_check or persons.<id>.bvn, with a reason you can show the person. A failed selfie video check has code identity_check_failed, or a more specific code. To let the person try again, send them a selfie check link. See Onboarding.