Skip to main content

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 id is the entity UUID — store it for all subsequent calls.
  • Review createdUbos in the response; directors from CIPC may already exist and need only completion steps.
  • sanctionsSearchResults contains 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"
}
FieldRequiredDescription
entityRegNameYesRegistered company name
entityRegNumberYesCIPC registration number
entityTypeYesID from GET /api/v1/settings/entity-types
clientReferenceNoYour external reference (recommended for CRM linking)
externalSystemIdNoYour 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
}
FieldDescription
adverseSearchKYCtrue if adverse search results require attention
pepConfirmReviewedtrue when PEP and sanctions review is complete

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.

  1. View directors and membersGET /api/v1/entity/:id/directors-and-members
  2. Add or edit usersPOST .../ubo-create for new people, or PATCH .../director-ubo-update for 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.

  1. Build the organigram — capture individuals, corporate entities, and trusts in the ownership tree. Maintain the full entityOrganigram JSON in your system (draft saves as needed); do not use incremental organigram/nodes APIs.
  2. Verify the organigram (Mandatory)PATCH /api/v1/entity/:id/verify-organigram when 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.

  1. 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.

StepActionEndpointNotes
1Update UBOPATCH .../director-ubo-updateComplete personal data for an existing UBO (CIPC pre-created, organigram verify, or previously added)
1bAdd new UBOPOST .../ubo-createOnly when showOrganigram is false — not available after organigram verify
2PEP confirmPATCH .../ubo-pep-confirmRecord PEP and sanctions review; response includes UBO riskStatus for document rules
3Upload documentsPOST .../ubo-doc-uploadOne file per request; repeat until all required documents are uploaded
4Confirm documentsPATCH .../ubo-doc-confirmCall 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

important

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

  1. Read riskStatus from the shareholding-confirm response.
  2. Filter entity-docs / entity-doc-types to get the full list of required entity documents for that risk band.
  3. Call entity-doc once per required document (one file per request).
  4. Call entity-doc-confirm only 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)

ActionEndpoint
Get as base64GET /api/v1/entity/:id/entity-doc:id = entityDocID
DeleteDELETE /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"
}
DecisionNotes
ApprovedEntity onboarded; optional approvedComments
DeclinedRequires declinedComments
ReferRequires referComments; returns to review
In ProgressDecision 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.

QueryResponse
Omitted (default)JSON with base64Content, mimeType, filename
format=binaryRaw 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

VersionDateNotes
1.02026-06-08Initial release

Support

For technical support and questions:


This documentation is maintained by the eFICA development team. For updates and corrections, please contact your account manager.

Last Updated: 8 June 2026