Manage users and KYC
User management lets tenant partners create and manage users, upload KYC/KYB documents, and issue per-user scoped tokens. Every endpoint needs a JWT bearer token — see Authentication for tenant vs. user-scoped tokens.
/profile for their own data rather than routing through /users/:id — the
profile endpoints act on whoever holds the token.| Use case | Endpoint | Token |
|---|---|---|
| User edits their own profile | PATCH /profile | User token |
| User reads their own profile | GET /profile | User token |
| User manages their own M-Pesa | POST/PATCH/DELETE /profile/mpesa | User token |
| User uploads a KYC document | POST /users/{userId}/kyc-documents | User token |
| User creates a payment | POST /payments | User token |
| Admin reads any user's profile | GET /users/{userId} | Tenant token |
| Admin edits any user's profile | PATCH /users/{userId} | Tenant token |
| Admin disables a user | DELETE /users/{userId} | Tenant token |
| Admin lists all users | GET /users | Tenant token |
The user object
Create User needs only an email and name; everything else is filled in later via
Update User and returned by Get User.
individual or business{
"userId": "ccc00000-0000-0000-0000-000000000003",
"email": "alice@example.com",
"entityType": "individual",
"firstName": "Alice",
"middleName": null,
"lastName": "Smith",
"dob": "1990-01-15",
"nationality": ["GBR"],
"birthCountry": "GBR",
"address": {
"line1": "1 Example Street",
"line2": null,
"city": "London",
"state": null,
"postalCode": "EC1A 1BB",
"country": "GBR"
},
"idDocumentType": "passport",
"idDocumentNumber": "123456789",
"sector": "technology",
"business": null,
"businessPersons": [],
"createdAt": "2026-05-07T12:00:00.000Z",
"updatedAt": "2026-05-07T12:00:00.000Z"
}The business object
Sent in business when entityType is business. The address
shape below is reused by registeredAddress, tradingAddress, and each
person's address.
{
"companyName": "Acme Ltd",
"tradingName": "Acme",
"type": "LIMITED",
"registrationNumber": "12345678",
"registrationDate": "2018-04-15",
"phone": "+442071234567",
"website": "https://acme.example",
"registeredAddress": {
"line1": "1 Example Street",
"city": "London",
"postalCode": "EC1A 1BB",
"country": "GBR"
}
}Business persons
The businessPersons array lists directors, shareholders, UBOs and secretaries. At
least one director and one ubo are required.
[
{
"types": ["director", "ubo"],
"ownership": 100,
"person": {
"firstName": "Alice",
"lastName": "Smith",
"dob": "1984-12-08",
"nationality": ["GBR"],
"birthCountry": "GBR",
"idDocumentType": "PASSPORT",
"idDocumentNumber": "123456789"
}
}
]Endpoints
Issue user token
Mint a 24-hour user-scoped JWT tied to a specific user — it can only read and write that one user's data. Requires a tenant token.
400 userId missing · 401 invalid token · 403 no
tenant / caller used a user token · 404 user not found / cross-tenant / inactive.{ "userId": "ccc00000-0000-0000-0000-000000000003" }Create user
Create a user under the caller's tenant. Idempotent — returns the existing record (200)
if the email is already registered, otherwise 201. Requires a tenant token.
{
"email": "alice@example.com",
"name": "Alice Smith"
}List users
List users in the caller's tenant. Disabled users are excluded by default. offsetMarker
is null on the last page; pass it as offset for the next page. Requires a
tenant token.
active (default) or inactive{
"items": [
{
"id": "5605d7f3-8cf3-4e68-a4d7-e816284098be",
"userId": "ccc00000-0000-0000-0000-000000000003",
"email": "alice@example.com",
"name": "Alice Smith",
"userRole": "Normal User",
"status": "Active",
"createdAt": "2026-05-07T12:00:00.000Z",
"updatedAt": "2026-05-07T12:00:00.000Z"
}
],
"offsetMarker": "eyJhIjoiYiJ9"
}Retrieve a user
Read a user's full profile (the user object above). A user token is accepted only
when its userId claim matches the path. Cross-tenant and disabled users both return
404.
{
"userId": "ccc00000-0000-0000-0000-000000000003",
"email": "alice@example.com",
"entityType": "individual",
"firstName": "Alice",
"middleName": null,
"lastName": "Smith",
"dob": "1990-01-15",
"nationality": ["GBR"],
"birthCountry": "GBR",
"address": {
"line1": "1 Example Street",
"line2": null,
"city": "London",
"state": null,
"postalCode": "EC1A 1BB",
"country": "GBR"
},
"idDocumentType": "passport",
"idDocumentNumber": "123456789",
"sector": "technology",
"business": null,
"businessPersons": [],
"createdAt": "2026-05-07T12:00:00.000Z",
"updatedAt": "2026-05-07T12:00:00.000Z"
}Update a user
Patch a profile — include only the fields you want to change. Address is set with flat
addressLine1/addressCity/… fields. When entityType is
business, business is required and businessPersons must include
at least one director and one UBO. Unknown fields return 400. User tokens must match the
path userId.
{
"firstName": "Alice",
"lastName": "Smith",
"dob": "1990-01-15",
"nationality": ["GBR"],
"addressLine1": "1 Example Street",
"addressCity": "London",
"addressPostalCode": "EC1A 1BB",
"addressCountry": "GBR",
"idDocumentType": "passport",
"idDocumentNumber": "123456789"
}Delete (disable) a user
Disables the user's Cognito account and sets status to Inactive. Disabled users are
hidden from List Users and return 404 on all user endpoints. A user cannot disable their
own account. Requires a tenant token.
400 can't disable own account · 401 invalid token · 403
no tenant / user token · 404 not found · 500 Cognito error (safe to retry).{
"id": "5605d7f3-8cf3-4e68-a4d7-e816284098be",
"userId": "ccc00000-0000-0000-0000-000000000003",
"email": "alice@example.com",
"name": "Alice Smith",
"status": "Inactive",
"updatedAt": "2026-05-07T18:00:00.000Z"
}Upload KYC document
Upload a KYC/KYB identity document as multipart/form-data. User tokens must match the
path userId.
| Value | Description |
|---|---|
passport | Passport |
national_id | National identity card |
drivers_license | Driver's licence |
proof_of_address | Utility bill, bank statement, etc. |
selfie | Selfie / liveness photo |
cert_of_incorp | Certificate of incorporation |
articles | Articles of association |
board_resolution | Board resolution |
ubo_register | UBO register |
other | Other supporting document |
documentType is case-insensitive and normalised to lowercase.
curl -X POST /users/{userId}/kyc-documents \
-H "Authorization: Bearer $TOKEN" \
-F "file=@passport.pdf;type=application/pdf" \
-F "documentType=passport"