Appearance
Mandates
List your mandates, newest first
GET
/api/v1/tenant/mandatesScope mandates:read
Paginated. Every mandate you registered, in any status. customerUid narrows within your own mandates; a customer outside your account matches nothing.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
customerUid | string | no | Customer the mandate is on |
status | enum | no | Mandate status |
page | number | no | Page to retrieve, 1-based. Example: 1 Default Value: 1 |
limit | number | no | Records per page. Values above the maximum are clamped, not rejected. Default Value: 20 Max Value: 100 |
sortBy | enum[] | no | Sort key. Repeat the param to sort by several fields; URL order sets precedence. Format: fieldName:DIRECTION Example: sortBy=createdAt:DESC&sortBy=id:ASC Default Value: createdAt:DESC,id:DESC Available Fields createdAt id Any other field is rejected with 400. |
Response · 200
| Field | Type | Description |
|---|---|---|
data | object[] | |
data[].uid | string | |
data[].customerUid | string | |
data[].walletUid | string | |
data[].beneficiaryEntityCode | string | |
data[].clientReference | object | null | Your reference, exactly as you supplied it. |
data[].amountRule | enum | One of fixed, up_to, formula. |
data[].fixedOrMaxAmountMinor | object | null | Minor units. |
data[].frequency | enum | One of one_time, daily, weekly, monthly, on_due_date, ad_hoc. |
data[].currency | string | |
data[].validFrom | string | |
data[].validTo | object | null | |
data[].status | enum | One of pending, active, suspended, revoked, expired, completed. |
data[].agreementUid | object | null | The acceptance recorded with this mandate. |
data[].replacesMandateUid | object | null | The mandate this one superseded. |
data[].revokedAt | object | null | When the mandate was revoked; null unless status is revoked. |
data[].createdAt | string | |
meta | object | |
meta.total | number | |
meta.page | number | |
meta.limit | number | |
meta.totalPages | number |
Example (generated from the schema)
json
{
"data": [
{
"uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"customerUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"walletUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"beneficiaryEntityCode": "string",
"clientReference": {},
"amountRule": "fixed",
"fixedOrMaxAmountMinor": {},
"frequency": "one_time",
"currency": "ETB",
"validFrom": "2026-09-03T10:15:30.000Z",
"validTo": {},
"status": "pending",
"agreementUid": {},
"replacesMandateUid": {},
"revokedAt": {},
"createdAt": "2026-09-03T10:15:30.000Z"
}
],
"meta": {
"total": 42,
"page": 1,
"limit": 20,
"totalPages": 3
}
}Errors
400 401 403 404 429 500 503 — one body shape for all of them, see Errors.
Register a customer's standing consent to be debited
POST
/api/v1/tenant/mandatesScope mandates:write · Write — send Idempotency-Key and X-Request-Id
Records the customer's acceptance of the consent document (agreementType = mandate) and the mandate that cites it, together or not at all. A customer authentication assertion is required because creating a mandate is itself consent (see the Customer authentication guide). The mandate is active when validFrom has passed and pending until then. beneficiaryEntityCode must identify a beneficiary that can collect mandate debits — a business with a collection wallet at Neo Wallet, or a beneficiary Neo Wallet has configured; anything else is a 422. replacesMandateUid supersedes an existing mandate atomically — it is revoked with reason superseded and its agreement moved to superseded, never edited. mandate.created is delivered to your webhook.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
customerUid | string | yes | The customer giving the standing consent. |
walletUid | string | yes | The customer's wallet the mandate debits. |
beneficiaryEntityCode | string | yes | Code of the beneficiary the debits pay; it must be a beneficiary set up with Neo Wallet to collect payments. At most 40 characters. |
clientReference | string | yes | Your own reference; stored and returned verbatim, never interpreted. |
amountRule | enum | yes | How the amount of each debit is bounded. One of fixed, up_to, formula. |
fixedOrMaxAmountMinor | object | no | Integer minor units (e.g. 250000 = ETB 2,500.00); the exact (fixed) or maximum (up_to) debit. Required unless amountRule is formula. Min 1. |
formula | object | no | The formula rule's document. No schema is defined yet, so formula is refused (422). |
frequency | enum | yes | How often the mandate may be debited. One of one_time, daily, weekly, monthly, on_due_date, ad_hoc. |
currency | string | yes | ISO-4217; must equal the wallet's currency. |
validFrom | string | yes | ISO-8601; the mandate is pending until then and active after. |
validTo | object | no | ISO-8601; must be after validFrom. Absent = open-ended. |
retryPolicy | object | no | Advisory, for your own use; Neo Wallet never schedules or retries a debit. |
agreement | object | yes | The consent document the customer accepted for this mandate. |
agreement.documentType | string | yes | Kind of document accepted, e.g. debit_mandate_terms. At most 40 characters. |
agreement.documentVersion | string | yes | Version of the accepted document. At most 40 characters. |
agreement.documentUri | string | yes | URI of the accepted document. At most 500 characters. |
agreement.documentHash | string | yes | SHA-256 of the accepted document, 64 hex characters. |
agreement.acceptedAt | string | yes | When the customer accepted the document, ISO-8601. |
agreement.acceptedChannel | string | yes | Your channel the consent was captured in; portal is not accepted. |
agreement.evidence | object | yes | Your proof of the acceptance; hashed and stored, never returned. |
assertion | object | yes | The customer's authentication for giving this consent. |
assertion.customerUid | string | yes | The customer who authenticated. |
assertion.identifierUid | object | no | The customer identifier the customer authenticated with, if known. |
assertion.assuranceLevel | enum | yes | How strongly your channel authenticated the customer. One of single_factor, two_factor, step_up. |
assertion.factor1Type | string | yes | The first factor used, e.g. pin, password, biometric. At most 32 characters. |
assertion.factor2Type | object | no | Required unless assuranceLevel is single_factor. At most 32 characters. |
assertion.authenticatedAt | string | yes | ISO-8601 — when the customer authenticated. |
assertion.expiresAt | string | yes | ISO-8601; by default at most 10 minutes after authenticatedAt. An expired assertion is refused (422). |
assertion.channelCode | string | yes | Your channel the customer authenticated on. At most 40 characters. |
assertion.deviceHash | object | no | SHA-256 hex of the device fingerprint, if your channel has one. |
assertion.sourceReference | string | yes | Opaque channel evidence reference (a session or login id). Presenting one again while it is live is allowed only for the same customer, and is audited. At most 160 characters. |
assertion.transactionBinding | object | no | Hash your channel computed over the request this assertion authorizes; a mismatch with the request received is refused. |
assertion.transactionBinding.hash | string | yes | SHA-256 hex (64 characters) over the canonical string of this request. |
assertion.transactionBinding.algorithm | enum | yes | One of sha256. |
assertion.transactionBinding.canonical | enum | yes | The field template hashed: the six values joined with |, UTF-8. The fifth is the payout destination, the mandate for a debit, or empty; clientReference is empty when absent. One of `customerUid |
replacesMandateUid | object | no | An existing mandate of this customer to supersede; it is revoked as this one is created. |
Example (generated from the schema)
json
{
"customerUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"walletUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"beneficiaryEntityCode": "string",
"clientReference": "string",
"amountRule": "fixed",
"frequency": "one_time",
"currency": "ETB",
"validFrom": "2026-09-03T10:15:30.000Z",
"agreement": {
"documentType": "string",
"documentVersion": "string",
"documentUri": "string",
"documentHash": "string",
"acceptedAt": "2026-09-03T10:15:30.000Z",
"acceptedChannel": "string",
"evidence": {}
},
"assertion": {
"customerUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"assuranceLevel": "single_factor",
"factor1Type": "string",
"authenticatedAt": "2026-09-03T10:15:30.000Z",
"expiresAt": "2026-09-03T10:15:30.000Z",
"channelCode": "string",
"sourceReference": "string"
}
}Response · 201
| Field | Type | Description |
|---|---|---|
uid | string | |
customerUid | string | |
walletUid | string | |
beneficiaryEntityCode | string | |
clientReference | object | null | Your reference, exactly as you supplied it. |
amountRule | enum | One of fixed, up_to, formula. |
fixedOrMaxAmountMinor | object | null | Minor units. |
frequency | enum | One of one_time, daily, weekly, monthly, on_due_date, ad_hoc. |
currency | string | |
validFrom | string | |
validTo | object | null | |
status | enum | One of pending, active, suspended, revoked, expired, completed. |
agreementUid | object | null | The acceptance recorded with this mandate. |
replacesMandateUid | object | null | The mandate this one superseded. |
revokedAt | object | null | When the mandate was revoked; null unless status is revoked. |
createdAt | string |
Example (generated from the schema)
json
{
"uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"customerUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"walletUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"beneficiaryEntityCode": "string",
"clientReference": {},
"amountRule": "fixed",
"fixedOrMaxAmountMinor": {},
"frequency": "one_time",
"currency": "ETB",
"validFrom": "2026-09-03T10:15:30.000Z",
"validTo": {},
"status": "pending",
"agreementUid": {},
"replacesMandateUid": {},
"revokedAt": {},
"createdAt": "2026-09-03T10:15:30.000Z"
}Errors
400 401 403 404 409 422 429 500 503 — one body shape for all of them, see Errors.
Get one of your mandates
GET
/api/v1/tenant/mandates/{uid}Scope mandates:read
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
uid | string | yes |
Response · 200
| Field | Type | Description |
|---|---|---|
uid | string | |
customerUid | string | |
walletUid | string | |
beneficiaryEntityCode | string | |
clientReference | object | null | Your reference, exactly as you supplied it. |
amountRule | enum | One of fixed, up_to, formula. |
fixedOrMaxAmountMinor | object | null | Minor units. |
frequency | enum | One of one_time, daily, weekly, monthly, on_due_date, ad_hoc. |
currency | string | |
validFrom | string | |
validTo | object | null | |
status | enum | One of pending, active, suspended, revoked, expired, completed. |
agreementUid | object | null | The acceptance recorded with this mandate. |
replacesMandateUid | object | null | The mandate this one superseded. |
revokedAt | object | null | When the mandate was revoked; null unless status is revoked. |
createdAt | string |
Example (generated from the schema)
json
{
"uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"customerUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"walletUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"beneficiaryEntityCode": "string",
"clientReference": {},
"amountRule": "fixed",
"fixedOrMaxAmountMinor": {},
"frequency": "one_time",
"currency": "ETB",
"validFrom": "2026-09-03T10:15:30.000Z",
"validTo": {},
"status": "pending",
"agreementUid": {},
"replacesMandateUid": {},
"revokedAt": {},
"createdAt": "2026-09-03T10:15:30.000Z"
}Errors
400 401 403 404 429 500 503 — one body shape for all of them, see Errors.
Revoke one of your mandates
POST
/api/v1/tenant/mandates/{uid}/revokeScope mandates:write · Write — send Idempotency-Key and X-Request-Id
reason is always required and recorded. Without assertion you revoke on your own authority; with one, the revocation is recorded as the customer's, and the assertion must be the mandate's customer's (see the Customer authentication guide). The mandate moves to revoked with revokedAt, the acceptance it cites moves to revoked, and mandate.revoked is delivered to your webhook. A revoked mandate is terminal — it is never reactivated; register a new one instead. Revoking a mandate that is already revoked, expired or completed is a 409.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
uid | string | yes |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | yes | Why the mandate is revoked; recorded on the mandate and the audit trail. At most 500 characters. |
assertion | object | no | The customer's authentication when you revoke on the customer's behalf; it must be for the mandate's customer. Omit when you revoke on your own authority. |
assertion.customerUid | string | yes | The customer who authenticated. |
assertion.identifierUid | object | no | The customer identifier the customer authenticated with, if known. |
assertion.assuranceLevel | enum | yes | How strongly your channel authenticated the customer. One of single_factor, two_factor, step_up. |
assertion.factor1Type | string | yes | The first factor used, e.g. pin, password, biometric. At most 32 characters. |
assertion.factor2Type | object | no | Required unless assuranceLevel is single_factor. At most 32 characters. |
assertion.authenticatedAt | string | yes | ISO-8601 — when the customer authenticated. |
assertion.expiresAt | string | yes | ISO-8601; by default at most 10 minutes after authenticatedAt. An expired assertion is refused (422). |
assertion.channelCode | string | yes | Your channel the customer authenticated on. At most 40 characters. |
assertion.deviceHash | object | no | SHA-256 hex of the device fingerprint, if your channel has one. |
assertion.sourceReference | string | yes | Opaque channel evidence reference (a session or login id). Presenting one again while it is live is allowed only for the same customer, and is audited. At most 160 characters. |
assertion.transactionBinding | object | no | Hash your channel computed over the request this assertion authorizes; a mismatch with the request received is refused. |
assertion.transactionBinding.hash | string | yes | SHA-256 hex (64 characters) over the canonical string of this request. |
assertion.transactionBinding.algorithm | enum | yes | One of sha256. |
assertion.transactionBinding.canonical | enum | yes | The field template hashed: the six values joined with |, UTF-8. The fifth is the payout destination, the mandate for a debit, or empty; clientReference is empty when absent. One of `customerUid |
Example (generated from the schema)
json
{
"reason": "string"
}Response · 200
| Field | Type | Description |
|---|---|---|
uid | string | |
customerUid | string | |
walletUid | string | |
beneficiaryEntityCode | string | |
clientReference | object | null | Your reference, exactly as you supplied it. |
amountRule | enum | One of fixed, up_to, formula. |
fixedOrMaxAmountMinor | object | null | Minor units. |
frequency | enum | One of one_time, daily, weekly, monthly, on_due_date, ad_hoc. |
currency | string | |
validFrom | string | |
validTo | object | null | |
status | enum | One of pending, active, suspended, revoked, expired, completed. |
agreementUid | object | null | The acceptance recorded with this mandate. |
replacesMandateUid | object | null | The mandate this one superseded. |
revokedAt | object | null | When the mandate was revoked; null unless status is revoked. |
createdAt | string |
Example (generated from the schema)
json
{
"uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"customerUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"walletUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"beneficiaryEntityCode": "string",
"clientReference": {},
"amountRule": "fixed",
"fixedOrMaxAmountMinor": {},
"frequency": "one_time",
"currency": "ETB",
"validFrom": "2026-09-03T10:15:30.000Z",
"validTo": {},
"status": "pending",
"agreementUid": {},
"replacesMandateUid": {},
"revokedAt": {},
"createdAt": "2026-09-03T10:15:30.000Z"
}Errors
400 401 403 404 409 422 429 500 503 — one body shape for all of them, see Errors.
Debit a wallet on a standing mandate, customer not present
POST
/api/v1/tenant/mandates/{uid}/debitsScope wallet-debits:write · Write — send Idempotency-Key and X-Request-Id
Moves the amount from the customer's wallet to the mandate's beneficiary, like POST /tenant/wallet-debits, but on the standing consent alone: no authentication assertion is accepted, and the mandate's frequency is enforced, so a second debit inside the current period, or any debit under a one_time mandate that has already completed one, is a 422. Neo Wallet never schedules these — you trigger each one, and the mandate's retryPolicy is advisory to you. A wallet that cannot fund the debit is not an error: the 201 carries the operation in state failed and you also receive wallet_debit.failed; a failed attempt does not consume the mandate's period, so it may be retried inside it under a fresh clientReference.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
uid | string | yes |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
walletUid | string | yes | The wallet to debit; must be the mandate's own wallet. |
amountMinor | number | yes | Positive integer minor units (e.g. 250000 = ETB 2,500.00); must satisfy the mandate's amount rule. Min 1. |
currency | string | yes | ISO-4217; must equal the wallet's currency. |
clientReference | string | yes | Your own reference; stored and returned verbatim, never interpreted; unique within your account. |
Example (generated from the schema)
json
{
"walletUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"amountMinor": 1,
"currency": "ETB",
"clientReference": "string"
}Response · 201
| Field | Type | Description |
|---|---|---|
uid | string | |
type | string | Operation type, e.g. wallet_credit, wallet_debit, payout_b2c or topup_c2b. |
state | string | Operation state: created, awaiting_authorization, authorized, held, processing, pending_provider, confirmed, failed, reversed or completed. |
amountMinor | number | Integer minor units (e.g. 250000 = ETB 2,500.00). |
feeMinor | number | Minor units. |
taxMinor | number | Minor units. |
currency | string | ISO-4217. |
clientReference | string | null | Your reference, exactly as you supplied it. |
createdAt | string | |
completedAt | string | null | When the operation first reached a final state. |
links | object | |
links.self | string | API-relative path of this operation (GET /tenant/operations/{uid}). |
links.receipt | string | API-relative path of its receipt (GET /tenant/operations/{uid}/receipt). |
Example (generated from the schema)
json
{
"uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
"type": "string",
"state": "string",
"amountMinor": 0,
"feeMinor": 0,
"taxMinor": 0,
"currency": "ETB",
"clientReference": "string",
"createdAt": "2026-09-03T10:15:30.000Z",
"completedAt": "2026-09-03T10:15:30.000Z",
"links": {
"self": "string",
"receipt": "string"
}
}Errors
400 401 403 404 409 422 429 500 503 — one body shape for all of them, see Errors.