Paymoja Verification

KYC & KYB verification API for the Paymoja platform. Verify individuals and businesses before granting access to financial features.

Base URL: /api/v1/verification/
Version 1.0
South Africa (ThisIsMe provider)

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

ScopeAccess
verification:readRead cases, checks, status
verification:writeCreate cases, submit, upload documents
fullFull access to all endpoints

Verification Workflow

Cases follow a linear lifecycle. Create a case, attach documents, then submit for automated checks.

1Create Case
2Upload Docs
3Add Directors
4Submit
5Checks Run
6Result
ℹ️ Step 3 (Add Directors) is only required for KYB cases. Steps 2 and 3 can be done in any order before submitting.

Cases

GET /api/v1/verification/cases/ List verification cases

Retrieve all verification cases for your organization.

Query Parameters

ParameterTypeDescription
organizationUUIDFilter by organization ID
statusstringFilter by case status
case_typestringkyc 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"
    }
  ]
}
POST /api/v1/verification/cases/ Create a new case

Create a new KYC or KYB verification case in draft status.

Request Body

FieldTypeDescription
organization_idUUIDrequiredOrganization to verify
case_typestringrequiredkyc or kyb
subject_typestringoptionalindividual (default), company, trust, ngo
metadataobjectoptionalSubject data — see metadata fields below
directorsarrayoptionalDirectors/UBOs for KYB cases

KYC Metadata Fields

FieldDescription
identity_numberSouth African ID number or passport number
first_nameSubject's first name
last_nameSubject's last name
account_numberBank account number (for AVS checks)
account_typeBank account type
branch_codeBank branch code

KYB Metadata Fields

FieldDescription
registration_numberCIPC company registration number
vat_numberVAT registration number
account_numberCompany bank account
account_typeAccount type
branch_codeBranch 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"
}
GET /api/v1/verification/cases/{id}/ Get case details

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": [ ... ]
}
POST /api/v1/verification/cases/{id}/submit/ Submit for verification

Submit a draft case. This initiates all required checks with the verification provider automatically. Upload documents before submitting.

⚠️ Case must be in 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
}
POST /api/v1/verification/cases/{id}/review/ Approve or reject

Manually approve or reject a case after review.

Request Body

FieldTypeDescription
actionstringrequiredapprove or reject
notesstringoptionalReview notes
validity_daysintegeroptionalDays until expiry (default: 365, approve only)

Example

{
  "action": "approve",
  "notes": "All checks passed",
  "validity_days": 365
}
POST /api/v1/verification/cases/{id}/documents/ Upload a document

Upload a supporting document to a case. Use multipart/form-data.

Form Fields

FieldTypeDescription
document_typestringrequiredSee document types
filefilerequiredThe document file
check_idUUIDoptionalLink 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"
}
POST /api/v1/verification/cases/{id}/directors/ Add director/UBO (KYB)

Add a director or Ultimate Beneficial Owner to a KYB case.

Request Body

FieldTypeDescription
first_namestringrequiredDirector's first name
last_namestringrequiredDirector's last name
identity_numberstringoptionalID number (encrypted, write-only)
rolestringoptionaldirector, ubo, shareholder, secretary
ownership_percentagedecimaloptionalOwnership 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.

GET /api/v1/verification/checks/{id}/ Get check details

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"
}
POST /api/v1/verification/checks/{id}/retry/ Retry a failed check

Retry a check that failed due to a provider error. Only checks in failed status can be retried.

No request body required.

Status & Config

GET /api/v1/verification/status/ Organization verification status

Check whether an organization is verified. Use this for feature-gating decisions.

Query Parameters

ParameterTypeDescription
organizationUUIDOrganization 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-..."
}
GET /api/v1/verification/config/{id}/ Get verification config

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
}
PUT /api/v1/verification/config/{id}/ Update verification config

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

ValueDescription
kycKnow Your Customer — individual identity verification
kybKnow Your Business — company/entity verification

Case Statuses

draftCreated, not yet submitted
submittedSubmitted, awaiting processing
in_progressChecks are running
pending_reviewAwaiting manual review
approvedVerification approved
rejectedVerification rejected
expiredPast expiration date

Check Statuses

StatusDescription
pendingNot yet sent to provider
processingSent, awaiting result
completedResult received
failedProvider error (can retry)
expiredData expired (>72 hours)

Check Results

ResultDescription
passVerification passed
failVerification failed
inconclusiveCannot determine — manual review needed
errorProvider error
pendingResult not yet available

Check Types

KYC (Individual)

TypeDescription
id_checkBasic SA ID verification
id_check_plusExtended ID verification with additional data
selfieSelfie-to-ID photo comparison
dha_photo_compDHA (Home Affairs) photo comparison
address_lookupAddress verification via ID number
avs_individualBank Account Verification Service
aml_riskAnti-Money Laundering risk search
kyc_combinedCombined KYC package
idscanID/passport document scan (OCR)
credit_checkConsumer credit score check
safpsSA Fraud Prevention Service check
fica_expressFICA Express compliance check

KYB (Business)

TypeDescription
company_lookupCIPC company registration lookup
director_lookupCompany director verification
vat_searchSARS VAT registration search
avs_companyCompany bank account verification
company_amlCompany AML check

Document Types

ValueDescription
national_idSouth African National ID
passportPassport
drivers_licenseDriver's License
selfie_photoSelfie Photo
business_registrationBusiness Registration Certificate
tax_certificateTax Certificate
proof_of_addressProof of Address
director_idDirector ID Document
ubo_idUBO ID Document
otherOther

Error Handling

StatusDescription
200Success
201Resource created
400Validation error or invalid state transition
401Invalid or missing authentication
403Insufficient scopes or verification required
404Resource 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

PathFeature
/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

PathFeature
/api/auth/Authentication
/api/billing/Billing & subscriptions
/api/v1/verification/Verification API
/api/user/profile/User profile
/api/organizations/Organization management

Security

FeatureDetail
PII EncryptionIdentity numbers encrypted at rest (AES-256 Fernet). Never returned in responses.
Document IntegrityAll uploads hashed with SHA-256 for tamper detection.
Audit TrailEvery action logged: case creation, submission, approval, document upload.
IP AllowlistingAPI keys can be restricted to specific IP addresses.
Key ExpirationAPI keys can have an expiration date. Expired keys are rejected.
Rate LimitingDefault 1,000 requests/hour per API key. Configurable per key.