Skip to main content
Requirements are what an account provides before a capability is enabled. Each capability requires certain fields, and the capability stays inactive until those fields are provided and accepted. 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.

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. provided says whether a value was submitted. A currently_due field with provided: true was rejected; with provided: false it was never filled in. A rejected field carries error_reason, written for display to the account holder. 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.

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.

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, .id_document, …) come from the capabilities the account holds: a recipient needs only a name, a merchant needs the full identity set. The id_number requirement is satisfied by any non-BVN entry in id_numbers. persons.bvn and persons.proof_of_address are eventually_due and do not block getting started; submit the bvn when asked by adding it to the list.

The business_profile shape

A merchant (an account that accepts payments) describes its business. These fields gate the payment-method capabilities (card_collection, ngn_card_collection, bank_transfer, …); a recipient does not owe 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/utilities/uploads (multipart), with the bytes and a scope that labels the upload:
  • identity_document — a person’s government ID.
  • account_requirement — a company filing or supporting evidence.
The response returns an upload_id. Accepted formats are the common image and PDF types; a PDF identity document is fine.
2

Attach it

Attach the uploaded file to the slot it satisfies, referencing the upload_id from the previous step. A person’s identity document attaches to that person: POST /v1/accounts/{account_id}/persons/{person_id}/documents with the upload_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/utilities/uploads.

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). A currency with no configured payout provider is rejected outright, whatever shape its object is otherwise in. Today that means:
  • NGN to a bank_account is payable.
  • USDT_TRC20 and USDT_BEP20 to a crypto_wallet are payable.
  • Nothing else is. In particular, mobile_money is a shape this field understands (it will check your phone_number and mobile_provider), but no payout provider is currently configured for any mobile money currency, so every mobile_money submission is rejected on currency. Do not build against GHS or KES mobile money payouts; they are not live.
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.

When a submission is rejected

A rejected write returns 400 with error_code: "INVALID_REQUIREMENT_FIELD" and an errors array, one entry per rejected field:
See Errors for the error envelope and Error Reference for every error_code.