Sub-merchants
Generated page, from the OpenAPI specification the gateway serves (
GET /v1/openapi.json). Do not edit it by hand: it is rewritten on every build.
| Method | Path | Description |
|---|---|---|
GET |
/v1/sub-merchants |
List your sub-merchants |
POST |
/v1/sub-merchants |
Create a sub-merchant |
GET |
/v1/sub-merchants/{id} |
Retrieve a sub-merchant |
PATCH |
/v1/sub-merchants/{id}/capabilities |
Change a sub-merchant's capabilities |
POST |
/v1/sub-merchants/{id}/status |
Suspend or reinstate a sub-merchant |
GET |
/v1/sub-merchants/{id}/kyb-file |
Retrieve a sub-merchant's KYB file |
POST |
/v1/sub-merchants/{id}/kyb/documents |
Upload a KYB document |
POST |
/v1/sub-merchants/{id}/kyb/persons |
Declare a beneficial owner or representative |
POST |
/v1/sub-merchants/{id}/kyb/verification |
Open identity verification for a person |
GET |
/v1/sub-merchants/{id}/api-keys |
List a sub-merchant's API keys |
POST |
/v1/sub-merchants/{id}/api-keys |
Issue an API key for a sub-merchant |
DELETE |
/v1/sub-merchants/{id}/api-keys/{keyId} |
Revoke a sub-merchant's API key |
GET /v1/sub-merchants
List your sub-merchants
The merchants owned by the calling account. ⚠️ Aggregator accounts only: an account that is not an aggregator (is_aggregator) receives 403 on every route in this section.
Responses
200— The list of sub-merchants. (object[])
POST /v1/sub-merchants
Create a sub-merchant
Creates a merchant owned by the calling account. ⚠️ Aggregator accounts only: an account that is not an aggregator (is_aggregator) receives 403 on every route in this section.
The parent is the CALLER and is never a field in the body: ownership is parent_client_id = caller, so a caller-supplied parent would be a tenancy check chosen by the party it constrains.
The create accepts NO capabilities: the sub-merchant is born with every capability off (refunds and TPE included), and a body that still carries capabilities is refused (422 capabilities_not_accepted) — set them afterwards with PATCH /v1/sub-merchants/{id}/capabilities, the one place they are managed. Nothing is dropped silently.
The pricing tier is not a choice — the child inherits the parent's tariff through the hierarchy. merchant_kind is almost always absent: a child inherits its parent's kind, and a kind that differs from the parent's is refused (422). The field exists so a caller can state what it believes and be corrected, never to choose.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
business_name |
string | yes | |
trading_name |
string | no | |
email |
string | no | |
phone |
string | no | |
country |
string | no | ISO alpha-2; empty = the parent's. |
business_type |
string | no | |
merchant_kind |
string | no | enterprise | sme; must equal the parent's. |
metadata |
object | no | 20 keys at most; key ≤ 64, value ≤ 500 characters. |
Responses
201— The sub-merchant that was created. (object)
GET /v1/sub-merchants/{id}
Retrieve a sub-merchant
The state of one child of the calling account. ⚠️ Aggregator accounts only: an account that is not an aggregator (is_aggregator) receives 403 on every route in this section.
Parameters
| Name | In | Required | Description |
|---|---|---|---|
id |
path | yes | The sub-merchant's PUBLIC id (a UUID), as returned by create and list. ⚠️ This is not the internal id, which never leaves the platform. |
Responses
200— The sub-merchant. (object)
PATCH /v1/sub-merchants/{id}/capabilities
Change a sub-merchant's capabilities
Nine capabilities are delegable, each switchable on or off, each under the parent's matching aggregator_manage_* right: tpe_enabled, cnp_enabled, mobile_money_enabled, internal_transfers_enabled, pi_enabled (BCEAO instant payments, under aggregator_manage_pi — recall has no capability of its own: the can_recall key scope rides pi_enabled), payout_enabled (under aggregator_manage_payout), refund_enabled (under aggregator_manage_refund), collection_accounts_enabled (under aggregator_manage_collection — the child's collection account is then opened automatically once its own KYB is approved) and settlement_schedule (the whole settlement schedule — the switch alone is refused without a cycle — and never the destination account). ⚠️ The child's own flag still decides at every gate: the parent asks, the child's capability and kyc_status answer. ⚠️ internal_transfers_enabled grants the child its OWN transfers; moving a child's money stays under aggregator_manage_payout. ⚠️ Aggregator accounts only: an account that is not an aggregator (is_aggregator) receives 403 on every route in this section.
Parameters
| Name | In | Required | Description |
|---|---|---|---|
id |
path | yes | The sub-merchant's PUBLIC id (a UUID), as returned by create and list. ⚠️ This is not the internal id, which never leaves the platform. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
tpe_enabled |
boolean | no | |
cnp_enabled |
boolean | no | |
mobile_money_enabled |
boolean | no | |
internal_transfers_enabled |
boolean | no | |
pi_enabled |
boolean | no | |
payout_enabled |
boolean | no | |
refund_enabled |
boolean | no | |
collection_accounts_enabled |
boolean | no | |
settlement_schedule |
object | no | L'horaire de règlement, en bloc. |
settlement_schedule.auto_settlement_enabled |
boolean | no | |
settlement_schedule.settlement_cycle |
string | no | |
settlement_schedule.settlement_delay_days |
integer | no | |
settlement_schedule.settlement_day_of_week |
integer | no | 0–6, dimanche = 0 (hebdomadaire) |
settlement_schedule.settlement_day_of_month |
integer | no | 1–28 (mensuel) |
settlement_schedule.settlement_time_of_day |
string | no | HH:MM |
settlement_schedule.settlement_min_minor |
string | no | unités mineures |
Responses
200— The updated sub-merchant. (object)
POST /v1/sub-merchants/{id}/status
Suspend or reinstate a sub-merchant
Changes a child's state. The reason is retained. ⚠️ Aggregator accounts only: an account that is not an aggregator (is_aggregator) receives 403 on every route in this section.
Parameters
| Name | In | Required | Description |
|---|---|---|---|
id |
path | yes | The sub-merchant's PUBLIC id (a UUID), as returned by create and list. ⚠️ This is not the internal id, which never leaves the platform. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
status |
string | yes | active | suspended. |
reason |
string | no |
Responses
200— The updated sub-merchant. (object)
GET /v1/sub-merchants/{id}/kyb-file
Retrieve a sub-merchant's KYB file
The child's compliance file: documents expected, documents received, people declared, and the verdict. ⚠️ Aggregator accounts only: an account that is not an aggregator (is_aggregator) receives 403 on every route in this section.
Parameters
| Name | In | Required | Description |
|---|---|---|---|
id |
path | yes | The sub-merchant's PUBLIC id (a UUID), as returned by create and list. ⚠️ This is not the internal id, which never leaves the platform. |
Responses
200— The KYB file.
POST /v1/sub-merchants/{id}/kyb/documents
Upload a KYB document
⚠️ multipart/form-data, not JSON — the document is a file. 10 MB maximum. ⚠️ Aggregator accounts only: an account that is not an aggregator (is_aggregator) receives 403 on every route in this section.
Which documents are expected depends on the child's country of registration; GET /v1/sub-merchants/{id}/kyb-file enumerates the ones still outstanding.
Parameters
| Name | In | Required | Description |
|---|---|---|---|
id |
path | yes | The sub-merchant's PUBLIC id (a UUID), as returned by create and list. ⚠️ This is not the internal id, which never leaves the platform. |
Request body
⚠️ multipart/form-data, not JSON.
| Field | Type | Required | Description |
|---|---|---|---|
file |
file | yes | The file, 10 MB at most. |
doc_type |
string | yes | The document's code, as enumerated by kyb-file. |
doc_number |
string | no | |
doc_country |
string | no | ISO alpha-2. |
issued_at |
string | no |
Responses
201— The document, once stored.
POST /v1/sub-merchants/{id}/kyb/persons
Declare a beneficial owner or representative
Declares a person on the child's file: beneficial owner, legal representative, director. ⚠️ Aggregator accounts only: an account that is not an aggregator (is_aggregator) receives 403 on every route in this section.
Parameters
| Name | In | Required | Description |
|---|---|---|---|
id |
path | yes | The sub-merchant's PUBLIC id (a UUID), as returned by create and list. ⚠️ This is not the internal id, which never leaves the platform. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
person_type |
string | yes | The role declared on the file. |
phone |
string | no | |
email |
string | no | |
role |
string | no | |
ownership_pct |
number | no | Ownership percentage. |
is_ubo |
boolean | no | Beneficial owner. |
is_pep |
boolean | no | Politically exposed person. |
Responses
201— The person, once declared.
POST /v1/sub-merchants/{id}/kyb/verification
Open identity verification for a person
Opens a verification session for a person already declared. The compliance portal resolves sessions by contact identifier, which is why phone / email sit beside person_id. ⚠️ Aggregator accounts only: an account that is not an aggregator (is_aggregator) receives 403 on every route in this section.
Parameters
| Name | In | Required | Description |
|---|---|---|---|
id |
path | yes | The sub-merchant's PUBLIC id (a UUID), as returned by create and list. ⚠️ This is not the internal id, which never leaves the platform. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
person_id |
string | yes | |
phone |
string | no | |
email |
string | no |
Responses
201— The verification session that was opened.
GET /v1/sub-merchants/{id}/api-keys
List a sub-merchant's API keys
The keys THIS account issued for this sub-merchant — metadata and effective scopes, never a secret. Keys the sub-merchant created itself are not listed: they are not the parent's. ⚠️ Aggregator accounts only: an account that is not an aggregator (is_aggregator) receives 403 on every route in this section. These three routes ALSO require the account's aggregator_manage_api_keys right (granted by neo-pays; 403 sub_merchant_api_keys_not_enabled otherwise) and, on the calling key, BOTH the can_manage_sub_merchants and can_manage_sub_merchant_api_keys scopes (403 insufficient_scope otherwise).
Parameters
| Name | In | Required | Description |
|---|---|---|---|
id |
path | yes | The sub-merchant's PUBLIC id (a UUID), as returned by create and list. ⚠️ This is not the internal id, which never leaves the platform. |
Responses
200— The keys this account issued for this sub-merchant. (object[])404— No sub-merchant of this account has this id.
POST /v1/sub-merchants/{id}/api-keys
Issue an API key for a sub-merchant
Issues an API key that belongs to the SUB-MERCHANT: it authenticates as the child, never as the parent. The secret (secret_key, and signing_secret when signing is required) is returned ONCE, in this response (Cache-Control: no-store); only its hash is kept.
The scopes are bounded by the SUB-MERCHANT's capabilities, not the parent's: a scope the child cannot exercise is refused (409 precondition_failed, naming the capability). can_use_pi and can_recall are therefore grantable as soon as the child has pi_enabled; can_recall implies can_use_pi.
⚠️ An unknown or misspelt scope is refused (422 unknown_scope), never ignored. The reserved scopes — can_use_tontine_pi, can_manage_sub_merchants, can_manage_collection_accounts and can_manage_sub_merchant_api_keys — can NEVER be carried by a sub-merchant key (403 reserved_scope).
A live key requires a sub-merchant in production (is_live); otherwise issue a test key. The issue is recorded on the sub-merchant's audit log, in the parent's name. ⚠️ Aggregator accounts only: an account that is not an aggregator (is_aggregator) receives 403 on every route in this section. These three routes ALSO require the account's aggregator_manage_api_keys right (granted by neo-pays; 403 sub_merchant_api_keys_not_enabled otherwise) and, on the calling key, BOTH the can_manage_sub_merchants and can_manage_sub_merchant_api_keys scopes (403 insufficient_scope otherwise).
Parameters
| Name | In | Required | Description |
|---|---|---|---|
id |
path | yes | The sub-merchant's PUBLIC id (a UUID), as returned by create and list. ⚠️ This is not the internal id, which never leaves the platform. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | |
key_type |
string | no | test by default. live requires a sub-merchant in production. |
scopes |
object | no | Scope name → boolean. An absent scope is false. |
scopes.can_initiate_payment |
boolean | no | |
scopes.can_payout |
boolean | no | |
scopes.can_refund |
boolean | no | |
scopes.can_read_transactions |
boolean | no | |
scopes.can_manage_webhooks |
boolean | no | |
scopes.can_read_customers |
boolean | no | |
scopes.can_write_customers |
boolean | no | |
scopes.can_manage_risk_lists |
boolean | no | |
scopes.can_read_collection_accounts |
boolean | no | |
scopes.can_use_pi |
boolean | no | |
scopes.can_recall |
boolean | no | |
scopes.can_cnp |
boolean | no | |
expires_in_days |
integer | no | 0 = no expiry. |
require_signature |
boolean | no | Require request signing (X-NeoPays-Signature) for this key. Permanent for the key's life. |
Responses
201— The issued key, and its secret — shown once. (object)403— Right, scope or reserved scope refused.404— No sub-merchant of this account has this id.409— The sub-merchant cannot exercise a requested scope, or is not in production for a live key.422— Unknown scope or invalid value.
DELETE /v1/sub-merchants/{id}/api-keys/{keyId}
Revoke a sub-merchant's API key
Immediate, permanent revocation of a key THIS account issued for this sub-merchant; it is refused from the next call. A key the parent did not issue receives the same 404 as an unknown one. Optional body {"reason": "…"}, kept with the revocation and on the sub-merchant's audit log. ⚠️ Aggregator accounts only: the calling key must carry can_manage_sub_merchants. Unlike issue and list, revoking needs NEITHER the aggregator_manage_api_keys right NOR the can_manage_sub_merchant_api_keys scope: a parent that has lost them can still revoke the keys it issued. When the link ends (unlinked, moved to another parent, closed), those keys are revoked automatically.
Parameters
| Name | In | Required | Description |
|---|---|---|---|
id |
path | yes | The sub-merchant's PUBLIC id (a UUID), as returned by create and list. ⚠️ This is not the internal id, which never leaves the platform. |
keyId |
path | yes | The key's id, as returned by the create and by the list of the sub-merchant's keys. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
reason |
string | no |
Responses
204— Key revoked.404— No active key issued by this account for this sub-merchant has this id.