Wallet
End users
Detail & Lookup

End User Detail & Lookup

Who calls these endpoints?External API (s2s)
Base path
/api/v1/wallet
Identity
Ed25519 request signature — X-Key-Id, X-Date, X-Nonce, Digest (when there is a body), X-Signature. ±5 min clock window, single-use nonce, optional per-key IP allow-list.
Authorization
The key must carry the wallet product and the wallet permission the call requires; a call without it gets 403 WALLET_INSUFFICIENT_SCOPE. Permissions per endpoint: Key permissions.
Cross-surface
Reading by id also exists in the merchant panel. These endpoints require a wallet read permission (wallet:enduser:read) on your key.

You can read an end user in two ways: by the id we issued, or by the externalCustomerId you sent at creation. Both endpoints return the same response.

Read by id

GET /api/v1/wallet/end-users/{id}

id must be a UUID; otherwise the request is rejected with 400.

curl "https://api-gateway.lunixi.com/api/v1/wallet/end-users/8d4c2a1f-3b6e-4f7a-9c5d-2e8b1a0f6c34" \
  -H "X-Key-Id: …" -H "X-Date: …" -H "X-Nonce: …" -H "X-Signature: …"

Lookup by externalCustomerId

GET /api/v1/wallet/end-users/by-external/{externalCustomerId}?programId={programId}
ParameterInRequiredDescription
externalCustomerIdpath✅The value you sent at creation. URL-encode it.
programIdquery✅Always required, because externalCustomerId is unique within a program. Without it the request is rejected with 400 WALLET_VALIDATION.

Matching is exact: a difference in letter case counts as a different customer; only leading and trailing whitespace is trimmed. If there is no match, the phone or any other field is not tried; the response is 404 WALLET_NOT_FOUND.

curl "https://api-gateway.lunixi.com/api/v1/wallet/end-users/by-external/crm-88213?programId=5b9e2f3a-7c41-4d8e-a6f2-9e1b3c4d5f60" \
  -H "X-Key-Id: …" -H "X-Date: …" -H "X-Nonce: …" -H "X-Signature: …"

Response

{
  "status": "success",
  "code": "SUCCESS",
  "message": "…",
  "data": {
    "endUser": {
      "id": "8d4c2a1f-3b6e-4f7a-9c5d-2e8b1a0f6c34",
      "programId": "5b9e2f3a-7c41-4d8e-a6f2-9e1b3c4d5f60",
      "accountType": "PERSONAL",
      "externalCustomerId": "crm-88213",
      "corporateAccountId": null,
      "corporateOffboardedAt": null,
      "kycRef": null,
      "kycLevelCache": "LOW",
      "kycLevelRefreshedAt": "2026-10-11T10:02:44.105Z",
      "phoneMasked": "9053******67",
      "displayNameMasked": "A*** Y*****",
      "merchantCategory": null,
      "status": "ACTIVE",
      "createdAt": "2026-10-11T09:15:02.418Z",
      "updatedAt": "2026-10-11T10:02:44.105Z"
    }
  },
  "requestId": "…"
}

Response fields

FieldTypeMeaning
idUUIDThe end user id. Used as {id} / endUserId in every other endpoint.
programIdUUIDThe program the user belongs to.
accountTypestringPERSONAL or BUSINESS.
externalCustomerIdstring | nullYour customer id; null if not sent.
corporateAccountIdUUID | nullThe employer (corporate account) the user is linked to as an employee. null = not linked to an employer.
corporateOffboardedAtdate | nullWhen the user last left an employer. If corporateAccountId is also set, the user was linked again after leaving. See Corporate Accounts.
kycRefstring | nullThe KYC reference you sent at creation.
kycLevelCachestring | nullThe recorded KYC level: LOW, SUBSTANTIAL or HIGH. null = no level recorded yet; the user is at level NONE.
kycLevelRefreshedAtdate | nullWhen the level was last recorded.
phoneMaskedstringMasked phone: first 4 and last 2 digits visible (9053******67). The clear phone is never returned.
displayNameMaskedstring | nullMasked display name (A*** Y*****); null if no name was sent.
merchantCategorystring | nullThe category of a business (BUSINESS) record; assigned in the merchant panel. null = no category assigned. Always null for personal records. In a program with a category rule, spending at an uncategorized business is rejected.
statusstringACTIVE, FROZEN or CLOSED — see Statuses.
createdAt, updatedAtdateISO 8601, UTC.

kycLevelCache is the recorded level. The effective level used in limit and permission decisions can be lower if your program defines a level validity period. Read the effective level with GET /api/v1/wallet/end-users/{id}/kyc-level: KYC Levels.

Both endpoints return the user regardless of status; FROZEN and CLOSED users can be read too. The balances endpoint lists the user's accounts and their account numbers (fullWalletNo).

404 behaviour

A user that belongs to another merchant and a user that does not exist at all get the same response:

{
  "status": "failure",
  "code": "WALLET_NOT_FOUND",
  "message": "…",
  "statusCode": 404,
  "requestId": "…"
}

The response does not reveal whether a record exists in another account. On by-external, sending a programId other than the one the user belongs to also returns 404.

Errors

HTTPCodeWhen
400—{id} is not a UUID
400WALLET_VALIDATIONprogramId missing on a by-external call
403WALLET_INSUFFICIENT_SCOPEThe key has no wallet read permission
404WALLET_NOT_FOUNDThe user does not exist or is not yours