Entity workflow — step-by-step guide
This page walks through the entity onboarding API in order. Return to the Entities overview for the high-level diagram and path selection.
Legend: steps marked Mandatory must be completed before onboarding decision. Steps marked Optional support review or correction.
Phase 1: Create and enrich the entity
Step 1: Create entity record (Mandatory)
Endpoint: POST /api/v1/entity
Creates a new entity FICA record. For South African entity types (foreignEntityType: false), eFICA looks up the registration number against CIPC and returns commercial data plus any directors pre-created from CIPC principal information.
Important:
- Insufficient account balance prevents record creation.
- The response
idis the entity UUID — store it for all subsequent calls. - Review
createdUbosin the response; directors from CIPC may already exist and need only completion steps. sanctionsSearchResultscontains entity-level screening results for your review in Step 3.
Request body:
{
"entityRegName": "Example Entity (Pty) Ltd",
"entityRegNumber": "2020/123456/07",
"entityType": 2,
"clientReference": "REF-001",
"externalSystemId": "your-system-id-optional"
}
| Field | Required | Description |
|---|---|---|
entityRegName | Yes | Registered company name |
entityRegNumber | Yes | CIPC registration number |
entityType | Yes | ID from GET /api/v1/settings/entity-types |
clientReference | No | Your external reference (recommended for CRM linking) |
externalSystemId | No | Your system's identifier for this entity |
Response (abbreviated):
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"entityRegName": "Example Entity (Pty) Ltd",
"entityRegNumber": "2020/123456/07",
"clientReference": "REF-001",
"ficaStatus": "In Progress",
"riskDescription": "Undetermined",
"cipcCommercialName": "EXAMPLE ENTITY (PTY) LTD",
"cipcCommercialResponse": { },
"sanctionsSearchResults": { },
"createdUbos": [
{
"uboUUID": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"firstName": "JOHN",
"lastName": "DOE",
"isDirector": true,
"shareholderType": "Individual"
}
]
}
Step 2: Update KYB details (Mandatory)
Endpoint: PATCH /api/v1/entity/:id/kyb
Captures Know Your Business information: trading name, address, industry, transaction profile, compliance flags, and custom questions.
Path parameter: :id = entity UUID
Request body (example):
{
"sameEntityName": true,
"entityTradingName": "Example Entity (Pty) Ltd",
"entityTelNumber": "0215558888",
"physicalAddress1": "123 Example Street",
"physicalAddress2": "Example Suburb",
"physicalAddress3": "Example City",
"zipCode": "8001",
"addressCountry": 189,
"countriesTransactedWith": [189],
"entityIndustry": 2,
"entityIndustryDescription": "",
"transactionFrequency": 1,
"transactionType": 31,
"transactionFundingSource": 31,
"sourceFundingDescription": "",
"transactionSourceWealth": 6,
"sourceWealthDescription": "",
"additionalVerification": false,
"additionalVerificationReason": "",
"transactionConsistent": "true",
"transactionInconsistantReason": "",
"entityDetailsConfirmed": true,
"customQuestions": {},
"userID": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
}
Lookup IDs for countries, industries, and transaction fields via the Settings Guide.
Response:
{ "success": true }
Step 3: Entity sanctions review (Mandatory)
Endpoint: PATCH /api/v1/entity/:id/sanctions-review
Record your review of the entity-level sanctions and adverse media screening returned on create.
Path parameter: :id = entity UUID
Request body:
{
"adverseSearchKYC": false,
"pepConfirmReviewed": true
}
| Field | Description |
|---|---|
adverseSearchKYC | true if adverse search results require attention |
pepConfirmReviewed | true when PEP and sanctions review is complete |
Step 3a: Link external system ID (Optional)
Endpoint: PATCH /api/v1/entity/update-external-system-id
Use when your system assigns its own ID after the eFICA record exists.
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"externalSystemId": "CRM-98765"
}
Phase 2: Ownership structure and UBO verification
This phase branches on showOrganigram. The per-person UBO process is identical on both paths; only the setup before that loop differs.
Path A: No organigram (showOrganigram: false)
Typical for Close Corporations and similar structures.
- View directors and members —
GET /api/v1/entity/:id/directors-and-members - Add or edit users —
POST .../ubo-createfor new people, orPATCH .../director-ubo-updatefor existing ones (including CIPC pre-created directors)
Then complete the per-person UBO loop for every director, shareholder, and related party.
You may continue adding or editing UBOs on this path until you call shareholding-confirm.
Path B: Organigram (showOrganigram: true)
Typical for companies with layered or trust ownership.
- Build the organigram — capture individuals, corporate entities, and trusts in the ownership tree. Maintain the full
entityOrganigramJSON in your system (draft saves as needed); do not use incrementalorganigram/nodesAPIs. - Verify the organigram (Mandatory) —
PATCH /api/v1/entity/:id/verify-organigramwhen the structure is complete, submitting the full tree in one call
After verify, eFICA creates or links UBO records for every individual and trust member in the tree. You cannot add more UBOs after the organigram is verified.
- Complete the per-person UBO loop for every UBO individual and trust individual returned from the organigram.
See Organigram for the full organigram guide.
Per-person UBO loop
Repeat for each UBO and trust individual — there may be many. Steps 2 and 3 are the same on both paths; step 1 depends on showOrganigram.
| Step | Action | Endpoint | Notes |
|---|---|---|---|
| 1 | Update UBO | PATCH .../director-ubo-update | Complete personal data for an existing UBO (CIPC pre-created, organigram verify, or previously added) |
| 1b | Add new UBO | POST .../ubo-create | Only when showOrganigram is false — not available after organigram verify |
| 2 | PEP confirm | PATCH .../ubo-pep-confirm | Record PEP and sanctions review; response includes UBO riskStatus for document rules |
| 3 | Upload documents | POST .../ubo-doc-upload | One file per request; repeat until all required documents are uploaded |
| 4 | Confirm documents | PATCH .../ubo-doc-confirm | Call once per UBO, only after every required document is uploaded |
Full details: UBOs and Documents.
Confirm shareholding (Mandatory — both paths)
Endpoint: PATCH /api/v1/entity/:id/shareholding-confirm
Call this only after every UBO and trust individual has completed the three-step loop above. This applies to both showOrganigram: true and showOrganigram: false.
{ "ownershipConfirmation": true }
Response:
{
"message": "Shareholding confirmed successfully",
"riskStatus": "Low Risk",
"riskReason": "The entities calculated risk score (5) is within the low risk range. (1 to 14)"
}
The riskStatus returned here is used to determine required entity documents in Phase 3.
Phase 3: Entity documents
Do not start entity document upload until all UBO/trust individuals are complete and shareholding-confirm has succeeded.
See Documents for how to determine which documents to collect.
Workflow
- Read
riskStatusfrom theshareholding-confirmresponse. - Filter
entity-docs/entity-doc-typesto get the full list of required entity documents for that risk band. - Call
entity-doconce per required document (one file per request). - Call
entity-doc-confirmonly after every required entity document has been uploaded.
Upload entity documents (Mandatory — repeat per document)
Endpoint: POST /api/v1/entity/:id/entity-doc
Path parameter: :id = entity UUID
Each request uploads one file for one document group + type pair. Loop until every required document is on file.
Multipart form data:
file(binary, required)documents(JSON string, required):{"entityDocsID": 2, "entityDocTypesID": 1}additionalFicaDoc(optional)
cURL example:
curl -X POST "https://sandboxapi.efica.co.za/api/v1/entity/{entityUUID}/entity-doc" \
-H "Authorization: Bearer <token>" \
-F "file=@/path/to/document.pdf" \
-F 'documents={"entityDocsID": 2, "entityDocTypesID": 1}' \
-F "additionalFicaDoc=false"
Store each returned entityDocID if you need to view or delete a file before confirming.
View or delete entity documents (Optional)
| Action | Endpoint |
|---|---|
| Get as base64 | GET /api/v1/entity/:id/entity-doc — :id = entityDocID |
| Delete | DELETE /api/v1/entity/:id/entity-doc — :id = entityDocID |
Confirm entity documents (Mandatory — once, after all uploads)
Endpoint: PATCH /api/v1/entity/:id/entity-doc-confirm
Path parameter: :id = entity UUID
Call this only after every required entity document has been uploaded. Do not confirm after the first upload if more documents are still required.
{ "docsUploadedReviewed": true }
Phase 4: Onboarding decision
Update onboarding decision (Mandatory)
Endpoint: PATCH /api/v1/entity/:id/onboarding-decision
Path parameter: :id = entity UUID
{
"onboardingDecision": "Approved",
"approvedComments": "All checks passed",
"nextFicaReviewDate": "2027-06-01T00:00:00.000Z"
}
| Decision | Notes |
|---|---|
Approved | Entity onboarded; optional approvedComments |
Declined | Requires declinedComments |
Refer | Requires referComments; returns to review |
In Progress | Decision still pending |
Returns 403 if the entity is already approved or declined.
Phase 5: PDF report (Optional)
Endpoint: GET /api/v1/entity/:id/pdf-report
Returns the complete entity FICA application report.
| Query | Response |
|---|---|
| Omitted (default) | JSON with base64Content, mimeType, filename |
format=binary | Raw application/pdf bytes |
{
"base64Content": "JVBERi0xLjQKJeLjz9MK...",
"mimeType": "application/pdf",
"filename": "fica-report-f47ac10b-58cc-4372-a567-0e02b2c3d479.pdf"
}
Additional endpoints
Get entity details
Endpoint: GET /api/v1/entity/:id
Returns the full entity record including workflow completion flags. Use to poll onboarding progress.
List entities
Endpoint: GET /api/v1/entity
Paginated list with optional filters (take, skip, clientReference, ficaStatus, riskDescription, and more). Maximum 100 records per page.
Complete workflow example
const base = 'https://sandboxapi.efica.co.za/api/v1/entity';
const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };
// Step 1: Create
const createRes = await fetch(base, {
method: 'POST', headers,
body: JSON.stringify({
entityRegName: 'Example Entity (Pty) Ltd',
entityRegNumber: '2020/123456/07',
entityType: 2,
clientReference: 'REF-001',
}),
});
const { id: entityUUID, createdUbos } = await createRes.json();
// Step 2: KYB
await fetch(`${base}/${entityUUID}/kyb`, { method: 'PATCH', headers, body: JSON.stringify(kybData) });
// Step 3: Sanctions review
await fetch(`${base}/${entityUUID}/sanctions-review`, {
method: 'PATCH', headers,
body: JSON.stringify({ adverseSearchKYC: false, pepConfirmReviewed: true }),
});
// Phase 2: For each UBO / trust individual
for (const ubo of allUbos) {
// Step 1: Update existing UBO (director-ubo-update on both paths)
await fetch(`${base}/${ubo.uboUUID}/director-ubo-update`, {
method: 'PATCH', headers, body: JSON.stringify(uboData),
});
// Step 1b: ubo-create only when showOrganigram is false — not shown here
// Step 2: PEP confirm (determines required UBO documents)
const pepRes = await fetch(`${base}/${ubo.uboUUID}/ubo-pep-confirm`, {
method: 'PATCH', headers,
body: JSON.stringify({ adverseSearchKYC: false, pepConfirmReviewed: true }),
});
const { riskStatus } = await pepRes.json();
// Step 3: Upload each required document (one ubo-doc-upload per file)
for (const doc of requiredUboDocsFor(riskStatus)) {
const formData = new FormData();
formData.append('file', doc.file);
formData.append('documents', JSON.stringify({
individualDocsID: doc.groupId,
IndividualDocTypesID: doc.typeId,
}));
await fetch(`${base}/${ubo.uboUUID}/ubo-doc-upload`, {
method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: formData,
});
}
// Step 4: Confirm only after ALL required documents are uploaded
await fetch(`${base}/${ubo.uboUUID}/ubo-doc-confirm`, {
method: 'PATCH', headers,
body: JSON.stringify({ docsUploadedReviewed: true }),
});
}
// Shareholding confirm — both showOrganigram true and false (riskStatus drives entity docs)
const shareholdingRes = await fetch(`${base}/${entityUUID}/shareholding-confirm`, {
method: 'PATCH', headers,
body: JSON.stringify({ ownershipConfirmation: true }),
});
const { riskStatus: entityRiskStatus } = await shareholdingRes.json();
// Phase 3: Entity documents — upload each required file, then confirm once
for (const doc of requiredEntityDocsFor(entityRiskStatus)) {
const formData = new FormData();
formData.append('file', doc.file);
formData.append('documents', JSON.stringify({
entityDocsID: doc.groupId,
entityDocTypesID: doc.typeId,
}));
await fetch(`${base}/${entityUUID}/entity-doc`, {
method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: formData,
});
}
// Confirm only after ALL required entity documents are uploaded
await fetch(`${base}/${entityUUID}/entity-doc-confirm`, {
method: 'PATCH', headers,
body: JSON.stringify({ docsUploadedReviewed: true }),
});
// Step 4: Onboarding decision
await fetch(`${base}/${entityUUID}/onboarding-decision`, {
method: 'PATCH', headers,
body: JSON.stringify({ onboardingDecision: 'Approved' }),
});
Document history
| Version | Date | Notes |
|---|---|---|
| 1.0 | 2026-06-08 | Initial release |
Support
For technical support and questions:
- Email: melissa@efica.co.za
- Melissa will co-ordinate with the development team.
This documentation is maintained by the eFICA development team. For updates and corrections, please contact your account manager.
Last Updated: 8 June 2026