Paymoja Verification
KYC & KYB verification API for the Paymoja platform. Verify individuals and businesses before granting access to financial features.
Authentication
All endpoints require authentication. Partners use API keys; platform users use JWT tokens.
API Key (Partners)
Authorization: Api-Key cen_xxxx_xxxxxxxxxxxxxxxxxxxxx
Fallback header: X-API-Key
JWT (Platform Users)
Authorization: Bearer <access_token>
Required Scopes
| Scope | Access |
|---|---|
| verification:read | Read cases, checks, status |
| verification:write | Create cases, submit, upload documents |
| full | Full access to all endpoints |
Verification Workflow
Cases follow a linear lifecycle. Create a case, attach documents, then submit for automated checks.
Cases
Retrieve all verification cases for your organization.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| organization | UUID | Filter by organization ID |
| status | string | Filter by case status |
| case_type | string | kyc or kyb |
Response
{
"count": 1,
"results": [
{
"id": "a1b2c3d4-e5f6-...",
"organization": "org-uuid",
"organization_name": "Acme Corp",
"case_type": "kyc",
"case_type_display": "Know Your Customer",
"status": "approved",
"status_display": "Approved",
"risk_level": "low",
"checks_count": 3,
"checks_completed": 3,
"expires_at": "2027-02-11T12:00:00Z",
"created_at": "2026-02-11T10:00:00Z"
}
]
}
Create a new KYC or KYB verification case in draft status.
Request Body
| Field | Type | Description | |
|---|---|---|---|
| organization_id | UUID | required | Organization to verify |
| case_type | string | required | kyc or kyb |
| subject_type | string | optional | individual (default), company, trust, ngo |
| metadata | object | optional | Subject data — see metadata fields below |
| directors | array | optional | Directors/UBOs for KYB cases |
KYC Metadata Fields
| Field | Description |
|---|---|
| identity_number | South African ID number or passport number |
| first_name | Subject's first name |
| last_name | Subject's last name |
| account_number | Bank account number (for AVS checks) |
| account_type | Bank account type |
| branch_code | Bank branch code |
KYB Metadata Fields
| Field | Description |
|---|---|
| registration_number | CIPC company registration number |
| vat_number | VAT registration number |
| account_number | Company bank account |
| account_type | Account type |
| branch_code | Branch code |
Example Request
curl -X POST /api/v1/verification/cases/ \
-H "Authorization: Api-Key cen_xxxx_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"organization_id": "org-uuid",
"case_type": "kyc",
"subject_type": "individual",
"metadata": {
"identity_number": "9001015009087",
"first_name": "John",
"last_name": "Doe"
}
}'
Response 201 Created
{
"id": "a1b2c3d4-e5f6-...",
"case_type": "kyc",
"status": "draft",
"subject_type": "individual",
"risk_level": "unknown",
"metadata": { ... },
"checks": [],
"documents": [],
"directors": [],
"created_at": "2026-02-11T10:00:00Z"
}
Retrieve full details of a verification case including all checks, documents, and directors.
Response
{
"id": "a1b2c3d4-...",
"organization": "org-uuid",
"organization_name": "Acme Corp",
"case_type": "kyc",
"status": "in_progress",
"risk_level": "unknown",
"submitted_by": "user-uuid",
"submitted_by_name": "John Doe",
"expires_at": null,
"metadata": { ... },
"checks": [
{
"id": "check-uuid",
"check_type": "id_check",
"status": "completed",
"result": "pass",
"result_details": { ... }
}
],
"documents": [ ... ],
"directors": [ ... ]
}
Submit a draft case. This initiates all required checks with the verification provider automatically. Upload documents before submitting.
draft status. Submitting a case that has already been submitted will return a 400 error.
Response
{
"case": {
"id": "a1b2c3d4-...",
"status": "in_progress",
"checks": [
{
"check_type": "id_check",
"status": "processing",
"result": "pending"
}
]
},
"checks_initiated": 3
}
Manually approve or reject a case after review.
Request Body
| Field | Type | Description | |
|---|---|---|---|
| action | string | required | approve or reject |
| notes | string | optional | Review notes |
| validity_days | integer | optional | Days until expiry (default: 365, approve only) |
Example
{
"action": "approve",
"notes": "All checks passed",
"validity_days": 365
}
Upload a supporting document to a case. Use multipart/form-data.
Form Fields
| Field | Type | Description | |
|---|---|---|---|
| document_type | string | required | See document types |
| file | file | required | The document file |
| check_id | UUID | optional | Link to a specific check |
Example
curl -X POST /api/v1/verification/cases/{id}/documents/ \
-H "Authorization: Api-Key cen_xxxx_xxxxx" \
-F "document_type=national_id" \
-F "file=@id_document.pdf"
Response 201 Created
{
"id": "doc-uuid",
"document_type": "national_id",
"document_type_display": "National ID",
"file_name": "id_document.pdf",
"content_type": "application/pdf",
"created_at": "2026-02-11T10:15:00Z"
}
Add a director or Ultimate Beneficial Owner to a KYB case.
Request Body
| Field | Type | Description | |
|---|---|---|---|
| first_name | string | required | Director's first name |
| last_name | string | required | Director's last name |
| identity_number | string | optional | ID number (encrypted, write-only) |
| role | string | optional | director, ubo, shareholder, secretary |
| ownership_percentage | decimal | optional | Ownership stake (e.g. 25.50) |
Example
{
"first_name": "Jane",
"last_name": "Smith",
"identity_number": "8505025009083",
"role": "director",
"ownership_percentage": 51.00
}
identity_number is encrypted at rest and never returned in API responses.
Checks
Individual verification checks are created automatically when a case is submitted. Each check maps to a provider service call.
Response
{
"id": "check-uuid",
"check_type": "id_check",
"check_type_display": "ID Check",
"status": "completed",
"status_display": "Completed",
"provider": "thisisme",
"result": "pass",
"result_display": "Pass",
"result_details": {
"first_names": "JOHN",
"last_name": "DOE",
"id_number_valid": true,
"deceased": false
},
"error_message": "",
"started_at": "2026-02-11T10:30:00Z",
"completed_at": "2026-02-11T10:30:45Z"
}
Retry a check that failed due to a provider error. Only checks in failed status can be retried.
No request body required.
Status & Config
Check whether an organization is verified. Use this for feature-gating decisions.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| organization | UUID | Organization ID (uses primary org if omitted) |
Response (Verified)
{
"is_verified": true,
"status": "approved",
"message": "Verified",
"expires_at": "2027-02-11T12:00:00Z",
"case_id": "a1b2c3d4-..."
}
Response (Not Verified)
{
"is_verified": false,
"status": "in_progress",
"message": "Verification checks in progress",
"case_id": "a1b2c3d4-..."
}
Retrieve per-organization verification configuration.
Response
{
"id": "config-uuid",
"organization": "org-uuid",
"provider": "thisisme",
"is_active": true,
"required_kyc_checks": ["id_check", "selfie", "address_lookup"],
"required_kyb_checks": ["company_lookup", "director_lookup"],
"auto_approve_threshold": "none",
"verification_validity_days": 365
}
Update organization verification settings including required checks and auto-approve policy.
Request Body
{
"organization": "org-uuid",
"required_kyc_checks": ["id_check", "selfie", "aml_risk"],
"required_kyb_checks": ["company_lookup", "director_lookup", "company_aml"],
"auto_approve_threshold": "low",
"verification_validity_days": 180
}
Reference
Case Types
| Value | Description |
|---|---|
| kyc | Know Your Customer — individual identity verification |
| kyb | Know Your Business — company/entity verification |
Case Statuses
draftCreated, not yet submittedsubmittedSubmitted, awaiting processingin_progressChecks are runningpending_reviewAwaiting manual reviewapprovedVerification approvedrejectedVerification rejectedexpiredPast expiration dateCheck Statuses
| Status | Description |
|---|---|
| pending | Not yet sent to provider |
| processing | Sent, awaiting result |
| completed | Result received |
| failed | Provider error (can retry) |
| expired | Data expired (>72 hours) |
Check Results
| Result | Description |
|---|---|
| pass | Verification passed |
| fail | Verification failed |
| inconclusive | Cannot determine — manual review needed |
| error | Provider error |
| pending | Result not yet available |
Check Types
KYC (Individual)
| Type | Description |
|---|---|
| id_check | Basic SA ID verification |
| id_check_plus | Extended ID verification with additional data |
| selfie | Selfie-to-ID photo comparison |
| dha_photo_comp | DHA (Home Affairs) photo comparison |
| address_lookup | Address verification via ID number |
| avs_individual | Bank Account Verification Service |
| aml_risk | Anti-Money Laundering risk search |
| kyc_combined | Combined KYC package |
| idscan | ID/passport document scan (OCR) |
| credit_check | Consumer credit score check |
| safps | SA Fraud Prevention Service check |
| fica_express | FICA Express compliance check |
KYB (Business)
| Type | Description |
|---|---|
| company_lookup | CIPC company registration lookup |
| director_lookup | Company director verification |
| vat_search | SARS VAT registration search |
| avs_company | Company bank account verification |
| company_aml | Company AML check |
Document Types
| Value | Description |
|---|---|
| national_id | South African National ID |
| passport | Passport |
| drivers_license | Driver's License |
| selfie_photo | Selfie Photo |
| business_registration | Business Registration Certificate |
| tax_certificate | Tax Certificate |
| proof_of_address | Proof of Address |
| director_id | Director ID Document |
| ubo_id | UBO ID Document |
| other | Other |
Error Handling
| Status | Description |
|---|---|
| 200 | Success |
| 201 | Resource created |
| 400 | Validation error or invalid state transition |
| 401 | Invalid or missing authentication |
| 403 | Insufficient scopes or verification required |
| 404 | Resource not found |
Error Response Format
{
"detail": "Cannot submit case in 'Approved' status"
}
Validation Error Format
{
"case_type": ["This field is required."],
"organization_id": ["Must be a valid UUID."]
}
Feature Gating
When verification enforcement is enabled, unverified organizations receive a 403 on restricted endpoints:
{
"error": "verification_required",
"code": "verification_required",
"message": "Organization verification is required to access this feature",
"case_id": "a1b2c3d4-...",
"verification_status": "in_progress"
}
Restricted Endpoints
| Path | Feature |
|---|---|
| /api/v1/banking/ | Banking operations |
| /api/v1/expenses/ | Expense management |
| /api/v1/wallet/ | Wallet operations |
| /api/v1/xero/ | ERP sync |
| /api/v1/purchases/ | Purchase management |
Always Accessible
| Path | Feature |
|---|---|
| /api/auth/ | Authentication |
| /api/billing/ | Billing & subscriptions |
| /api/v1/verification/ | Verification API |
| /api/user/profile/ | User profile |
| /api/organizations/ | Organization management |
Security
| Feature | Detail |
|---|---|
| PII Encryption | Identity numbers encrypted at rest (AES-256 Fernet). Never returned in responses. |
| Document Integrity | All uploads hashed with SHA-256 for tamper detection. |
| Audit Trail | Every action logged: case creation, submission, approval, document upload. |
| IP Allowlisting | API keys can be restricted to specific IP addresses. |
| Key Expiration | API keys can have an expiration date. Expired keys are rejected. |
| Rate Limiting | Default 1,000 requests/hour per API key. Configurable per key. |