Skip to content

Customers

Onboard a customer and open its main wallet

POST/api/v1/tenant/customers

Scope customers:write · Write — send Idempotency-Key and X-Request-Id

Request body

FieldTypeRequiredDescription
customerTypeenumyesOne of person, organization.
displayNamestringyes
dateOfBirthobjectnoISO-8601 date.
regionCodeobjectno
preferredLocaleobjectnoBCP-47; defaults to en.
identifiersobject[]yes
identifiers[].classenumyesOne of contact, identity_document.
identifiers[].typestringyese.g. msisdn, email, national_id, passport.
identifiers[].valuestringyesClear-text value; reads return it masked.
identifiers[].isPrimarybooleanyes
identifiers[].verificationStatusenumyesOne of unverified, pending, verified, rejected, expired, revoked.
identifiers[].verifiedAtobjectnoISO-8601.
identifiers[].issuingCountryobjectnoISO-3166-1 alpha-2.
kycobjectyes
kyc.tierCodestringyesCode of an active KYC level, e.g. tier_1.
kyc.reviewTypeenumyesOne of onboarding.
kyc.verificationSourcestringyes
kyc.sourceReferencestringyesOpaque reference into your KYC system.
kyc.evidenceobjectyes
kyc.riskRatingenumyesOne of low, medium, high, prohibited.
agreementsobject[]yes
agreements[].agreementTypeenumyesOne of wallet_opening, privacy, fee_schedule, mandate, other.
agreements[].documentTypestringyes
agreements[].documentVersionstringyes
agreements[].documentUristringyes
agreements[].documentHashstringyesSHA-256 of the accepted document, 64 hex characters.
agreements[].acceptedAtstringyesISO-8601.
agreements[].acceptedChannelstringyesYour channel the consent was captured in.
agreements[].evidenceobjectyes
rulesProfileKeyobjectnoThe wallet profile to open the customer's wallet on; it decides what the wallet can do. Must be one your account is approved for. Omit it to use your account's default profile.
clientReferenceobjectnoYour own reference; stored and returned verbatim, never interpreted.
metadataobjectno
Example (generated from the schema)
json
{
  "customerType": "person",
  "displayName": "string",
  "identifiers": [
    {
      "class": "contact",
      "type": "string",
      "value": "string",
      "isPrimary": true,
      "verificationStatus": "unverified"
    }
  ],
  "kyc": {
    "tierCode": "string",
    "reviewType": "onboarding",
    "verificationSource": "string",
    "sourceReference": "string",
    "evidence": {},
    "riskRating": "low"
  },
  "agreements": [
    {
      "agreementType": "wallet_opening",
      "documentType": "string",
      "documentVersion": "string",
      "documentUri": "string",
      "documentHash": "string",
      "acceptedAt": "2026-09-03T10:15:30.000Z",
      "acceptedChannel": "string",
      "evidence": {}
    }
  ]
}

Response · 201

FieldTypeDescription
uidstring
statusenumOne of pending, active, restricted, suspended, deceased, closed.
customerTypeenumOne of person, organization.
displayNamestring
walletUidstringThe customer's main wallet; you can credit it immediately (POST /tenant/wallet-credits).
kycTierCodestringCode of the customer's KYC level, e.g. tier_1.
rulesProfileKeystringThe wallet profile the customer's wallet was opened on.
identifiersobject[]
identifiers[].uidstring
identifiers[].classenumOne of contact, identity_document.
identifiers[].typestring
identifiers[].maskedValuestringMasked; the raw value is never returned in this field.
identifiers[].isPrimaryboolean
identifiers[].verificationStatusenumOne of unverified, pending, verified, rejected, expired, revoked.
clientReferenceobject | nullYour reference, exactly as you supplied it.
createdAtstring
Example (generated from the schema)
json
{
  "uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
  "status": "pending",
  "customerType": "person",
  "displayName": "string",
  "walletUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
  "kycTierCode": "string",
  "rulesProfileKey": "string",
  "identifiers": [
    {
      "uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
      "class": "contact",
      "type": "string",
      "maskedValue": "string",
      "isPrimary": true,
      "verificationStatus": "unverified"
    }
  ],
  "clientReference": {},
  "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.

Customer summary: status, KYC, wallets, identifiers, agreements, mandates

GET/api/v1/tenant/customers/{uid}

Scope customers:read

Path parameters

NameTypeRequiredDescription
uidstringyes

Response · 200

FieldTypeDescription
uidstring
statusenumOne of pending, active, restricted, suspended, deceased, closed.
customerTypeenumOne of person, organization.
displayNamestring
kycobject | null
kyc.tierCodestringCode of the customer's KYC level, e.g. tier_1.
kyc.tierNamestringDisplay name of the KYC level.
kyc.riskRatingenumOne of low, medium, high, prohibited.
kyc.approvedAtstring
kyc.limitsobject
kyc.limits.currencystring
kyc.limits.maxBalanceMinornumberInteger minor units (e.g. 250000 = ETB 2,500.00).
kyc.limits.maxSingleTxnMinorobject | nullMinor units; null where the KYC level sets no limit.
kyc.limits.aggregateDailyLimitMinornumberMinor units.
kyc.limits.monthlyOutLimitMinorobject | nullMinor units; null where the KYC level sets no limit.
walletsobject[]
wallets[].walletUidstring
wallets[].purposeenumOne of main, incentive, commission, savings, other.
wallets[].statusenumOne of pending, active, debit_blocked, credit_blocked, frozen, dormant, closed.
wallets[].currencystring
wallets[].rulesProfileKeystringThe wallet profile the wallet was opened on.
wallets[].balanceobject | null
wallets[].balance.currencystring
wallets[].balance.currentMinornumberMinor units.
wallets[].balance.heldMinornumberMinor units.
wallets[].balance.availableMinornumberMinor units.
wallets[].balance.asOfstring
identifiersobject[]
identifiers[].uidstring
identifiers[].classenumOne of contact, identity_document.
identifiers[].typestring
identifiers[].maskedValuestringMasked; the raw value is never returned in this field.
identifiers[].isPrimaryboolean
identifiers[].verificationStatusenumOne of unverified, pending, verified, rejected, expired, revoked.
identifiers[].valueobject | nullClear text only when you hold the customers:pii scope and class is contact; identity documents are never unmasked. Every unmasked read is audited.
agreementsobject[]
agreements[].agreementTypeenumOne of wallet_opening, privacy, fee_schedule, mandate, other.
agreements[].documentVersionstring
agreements[].acceptedAtstring
mandatesCountnumberNumber of debit mandates you hold on this customer.
clientReferenceobject | nullYour reference, exactly as you supplied it.
createdAtstring
Example (generated from the schema)
json
{
  "uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
  "status": "pending",
  "customerType": "person",
  "displayName": "string",
  "kyc": {
    "tierCode": "string",
    "tierName": "string",
    "riskRating": "low",
    "approvedAt": "2026-09-03T10:15:30.000Z",
    "limits": {
      "currency": "ETB",
      "maxBalanceMinor": 0,
      "maxSingleTxnMinor": {},
      "aggregateDailyLimitMinor": 0,
      "monthlyOutLimitMinor": {}
    }
  },
  "wallets": [
    {
      "walletUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
      "purpose": "main",
      "status": "pending",
      "currency": "ETB",
      "rulesProfileKey": "string",
      "balance": {
        "currency": "ETB",
        "currentMinor": 0,
        "heldMinor": 0,
        "availableMinor": 0,
        "asOf": "string"
      }
    }
  ],
  "identifiers": [
    {
      "uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
      "class": "contact",
      "type": "string",
      "maskedValue": "string",
      "isPrimary": true,
      "verificationStatus": "unverified",
      "value": {}
    }
  ],
  "agreements": [
    {
      "agreementType": "wallet_opening",
      "documentVersion": "string",
      "acceptedAt": "2026-09-03T10:15:30.000Z"
    }
  ],
  "mandatesCount": 0,
  "clientReference": {},
  "createdAt": "2026-09-03T10:15:30.000Z"
}

Errors

400 401 403 404 429 500 503 — one body shape for all of them, see Errors.

List a customer's identifiers, masked

GET/api/v1/tenant/customers/{uid}/identifiers

Scope customers:read

Path parameters

NameTypeRequiredDescription
uidstringyes

Response · 200

FieldTypeDescription
dataobject[]
data[].uidstring
data[].classenumOne of contact, identity_document.
data[].typestring
data[].maskedValuestringMasked; the raw value is never returned in this field.
data[].isPrimaryboolean
data[].verificationStatusenumOne of unverified, pending, verified, rejected, expired, revoked.
data[].issuingCountryobject | null
data[].verifiedAtobject | null
data[].validFromstring
data[].validToobject | null
data[].payoutEligiblebooleanTrue for a verified contact identifier inside its validity window; only these can be a payout destination.
Example (generated from the schema)
json
{
  "data": [
    {
      "uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
      "class": "contact",
      "type": "string",
      "maskedValue": "string",
      "isPrimary": true,
      "verificationStatus": "unverified",
      "issuingCountry": {},
      "verifiedAt": {},
      "validFrom": "2026-09-03T10:15:30.000Z",
      "validTo": {},
      "payoutEligible": true
    }
  ]
}

Errors

400 401 403 404 429 500 503 — one body shape for all of them, see Errors.

Attach an identifier to a customer

POST/api/v1/tenant/customers/{uid}/identifiers

Scope customers:write · Write — send Idempotency-Key and X-Request-Id

Path parameters

NameTypeRequiredDescription
uidstringyes

Request body

FieldTypeRequiredDescription
classenumyesOne of contact, identity_document.
typestringyese.g. msisdn, email, national_id, passport.
valuestringyesClear-text value; reads return it masked.
isPrimarybooleannoDemotes the previous primary of the same class.
verificationStatusenumyesOne of unverified, pending, verified, rejected, expired, revoked.
verifiedAtobjectnoISO-8601.
issuingCountryobjectnoISO-3166-1 alpha-2.
Example (generated from the schema)
json
{
  "class": "contact",
  "type": "string",
  "value": "string",
  "verificationStatus": "unverified"
}

Response · 201

FieldTypeDescription
uidstring
classenumOne of contact, identity_document.
typestring
maskedValuestringMasked; the raw value is never returned in this field.
isPrimaryboolean
verificationStatusenumOne of unverified, pending, verified, rejected, expired, revoked.
issuingCountryobject | null
verifiedAtobject | null
validFromstring
validToobject | null
payoutEligiblebooleanTrue for a verified contact identifier inside its validity window; only these can be a payout destination.
Example (generated from the schema)
json
{
  "uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
  "class": "contact",
  "type": "string",
  "maskedValue": "string",
  "isPrimary": true,
  "verificationStatus": "unverified",
  "issuingCountry": {},
  "verifiedAt": {},
  "validFrom": "2026-09-03T10:15:30.000Z",
  "validTo": {},
  "payoutEligible": true
}

Errors

400 401 403 404 409 422 429 500 503 — one body shape for all of them, see Errors.

Update an identifier's verification state

PATCH/api/v1/tenant/customers/{uid}/identifiers/{iid}

Scope customers:write · Write — send Idempotency-Key and X-Request-Id

Path parameters

NameTypeRequiredDescription
uidstringyes
iidstringyes

Request body

FieldTypeRequiredDescription
verificationStatusenumyesOne of unverified, pending, verified, rejected, expired, revoked.
verifiedAtobject | nullyesISO-8601; required when verificationStatus is verified.
validToobjectnoISO-8601; retires the identifier at this instant.
Example (generated from the schema)
json
{
  "verificationStatus": "unverified",
  "verifiedAt": {}
}

Response · 200

FieldTypeDescription
uidstring
classenumOne of contact, identity_document.
typestring
maskedValuestringMasked; the raw value is never returned in this field.
isPrimaryboolean
verificationStatusenumOne of unverified, pending, verified, rejected, expired, revoked.
issuingCountryobject | null
verifiedAtobject | null
validFromstring
validToobject | null
payoutEligiblebooleanTrue for a verified contact identifier inside its validity window; only these can be a payout destination.
Example (generated from the schema)
json
{
  "uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
  "class": "contact",
  "type": "string",
  "maskedValue": "string",
  "isPrimary": true,
  "verificationStatus": "unverified",
  "issuingCountry": {},
  "verifiedAt": {},
  "validFrom": "2026-09-03T10:15:30.000Z",
  "validTo": {},
  "payoutEligible": true
}

Errors

400 401 403 404 409 422 429 500 503 — one body shape for all of them, see Errors.

List a customer's document acceptances

GET/api/v1/tenant/customers/{uid}/agreements

Scope customers:read

Path parameters

NameTypeRequiredDescription
uidstringyes

Response · 200

FieldTypeDescription
dataobject[]
data[].uidstring
data[].agreementTypeenumOne of wallet_opening, privacy, fee_schedule, mandate, other.
data[].documentTypestring
data[].documentVersionstring
data[].documentUristring
data[].documentHashstring
data[].statusenumOne of accepted, revoked, expired, superseded.
data[].acceptedAtstring
data[].acceptedChannelstring
data[].evidenceHashstringSHA-256 of the canonical consent evidence.
Example (generated from the schema)
json
{
  "data": [
    {
      "uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
      "agreementType": "wallet_opening",
      "documentType": "string",
      "documentVersion": "string",
      "documentUri": "string",
      "documentHash": "string",
      "status": "accepted",
      "acceptedAt": "2026-09-03T10:15:30.000Z",
      "acceptedChannel": "string",
      "evidenceHash": "string"
    }
  ]
}

Errors

400 401 403 404 429 500 503 — one body shape for all of them, see Errors.

Record a customer's acceptance of a document

POST/api/v1/tenant/customers/{uid}/agreements

Scope customers:write · Write — send Idempotency-Key and X-Request-Id

Path parameters

NameTypeRequiredDescription
uidstringyes

Request body

FieldTypeRequiredDescription
agreementTypeenumyesOne of wallet_opening, privacy, fee_schedule, mandate, other.
documentTypestringyese.g. terms, fee_schedule_v3, privacy_notice.
documentVersionstringyes
documentUristringyes
documentHashstringyesSHA-256 of the accepted document, 64 hex characters.
acceptedAtstringyesISO-8601.
acceptedChannelstringyesYour channel the consent was captured in; portal is not accepted.
evidenceobjectyesYour proof of the acceptance; never returned — evidenceHash stands in for it.
Example (generated from the schema)
json
{
  "agreementType": "wallet_opening",
  "documentType": "string",
  "documentVersion": "string",
  "documentUri": "string",
  "documentHash": "string",
  "acceptedAt": "2026-09-03T10:15:30.000Z",
  "acceptedChannel": "string",
  "evidence": {}
}

Response · 201

FieldTypeDescription
uidstring
agreementTypeenumOne of wallet_opening, privacy, fee_schedule, mandate, other.
documentTypestring
documentVersionstring
documentUristring
documentHashstring
statusenumOne of accepted, revoked, expired, superseded.
acceptedAtstring
acceptedChannelstring
evidenceHashstringSHA-256 of the canonical consent evidence.
Example (generated from the schema)
json
{
  "uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
  "agreementType": "wallet_opening",
  "documentType": "string",
  "documentVersion": "string",
  "documentUri": "string",
  "documentHash": "string",
  "status": "accepted",
  "acceptedAt": "2026-09-03T10:15:30.000Z",
  "acceptedChannel": "string",
  "evidenceHash": "string"
}

Errors

400 401 403 404 409 422 429 500 503 — one body shape for all of them, see Errors.

Submit a KYC update or KYC level change for a customer

POST/api/v1/tenant/customers/{uid}/kyc-versions

Scope customers:write · Write — send Idempotency-Key and X-Request-Id

Adds a new KYC version to the customer's history; earlier versions are never edited. A move to Level 1 (upgrade/downgrade to tier_1, risk rating not prohibited) is approved on your declaration alone and takes effect immediately: the previous version is superseded, the customer's wallets move to the new KYC level, a wallet whose balance is above that level's maximum is flagged credit_blocked, and customer.kyc_changed is delivered to your webhook. Every other submission is pending until Neo Wallet's compliance review approves or rejects it (the webhook fires then). An upgrade/downgrade must move the KYC level's maximum balance in the direction it names (422). Only one pending version per customer (409).

Path parameters

NameTypeRequiredDescription
uidstringyes

Request body

FieldTypeRequiredDescription
tierCodestringyesCode of an active KYC level, e.g. tier_1.
reviewTypeenumyesOne of upgrade, downgrade, periodic_review, event_driven_review.
verificationSourcestringyes
sourceReferencestringyesOpaque reference into your KYC system.
evidenceobjectyesYour verification evidence; never returned — evidenceHash stands in for it.
riskRatingenumyesOne of low, medium, high, prohibited.
Example (generated from the schema)
json
{
  "tierCode": "string",
  "reviewType": "upgrade",
  "verificationSource": "string",
  "sourceReference": "string",
  "evidence": {},
  "riskRating": "low"
}

Response · 201

FieldTypeDescription
uidstring
customerUidstring
tierCodestringCode of the KYC level this version asserts.
reviewTypeenumOne of upgrade, downgrade, periodic_review, event_driven_review.
stateenumapproved when your assertion is accepted outright (a move to Level 1); otherwise pending until Neo Wallet's compliance review decides, announced by the customer.kyc_changed webhook. One of pending, approved, rejected, expired, superseded.
verificationSourcestring
sourceReferencestring | null
riskRatingenum | nullOne of low, medium, high, prohibited.
evidenceHashstringSHA-256 of the canonical evidence JSON.
effectiveFromstring | nullSet when the version is approved.
createdAtstring
Example (generated from the schema)
json
{
  "uid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
  "customerUid": "01J9Z3K5X8QW2R4T6V8Y0B1C3D",
  "tierCode": "string",
  "reviewType": "upgrade",
  "state": "pending",
  "verificationSource": "string",
  "sourceReference": "string",
  "riskRating": "low",
  "evidenceHash": "string",
  "effectiveFrom": "string",
  "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.