Truztcube Docs

Business Verification (CAC)

Confirm a Nigerian company exists with the Corporate Affairs Commission and return its registry record — company details on Basic, plus directors and shareholding on Advance.

Business verification confirms a registration number against the Corporate Affairs Commission and returns the company's registry record. Use it to onboard a business customer, check a counterparty before contracting, or satisfy a KYB obligation.

Two tiers, priced separately:

TierEndpointReturns
BasicPOST /v1/cac/basicThe company record — name, status, address, registration date
AdvancePOST /v1/cac/advanceEverything in Basic, plus directors and shareholding

Both accept the same request body. They differ in what comes back, not in what you send.

Your first call

curl -X POST https://api.truztcube.com/v1/cac/basic \
  -H "x-api-key: sk_live_your_key" \
  -H "Idempotency-Key: kyb-onboarding-4821" \
  -H "Content-Type: application/json" \
  -d '{
    "rcNumber": "092932",
    "companyType": "RC",
    "purpose": "KYB_ONBOARDING"
  }'

Request

FieldRequiredNotes
rcNumberyesRegistration number as issued by the CAC. Leading zeros are significant — send 092932, not 92932
purposeyesWhy you are making this check. Fixed vocabulary, see below
companyTypenoRegistration classification. Defaults to RC
companyNamenoRefines the match when the registry returns more than one candidate

companyType

ValueMeaning
RCRegistered company (default)
BNBusiness name
ITIncorporated trustees
LPLimited partnership
LLPLimited liability partnership

Send the classification the number was issued under. An RC number looked up as BN will not resolve.

purpose

One of KYB_ONBOARDING, CREDIT_ASSESSMENT, PROCUREMENT_DUE_DILIGENCE, AML_SCREENING, PARTNER_VERIFICATION.

There is deliberately no catch-all value. purpose is recorded against the verification so that a later data-protection enquiry — particularly about director data on Advance — can be answered from the record rather than from memory. Pick the one that describes the actual reason for the check.

Headers

HeaderRequiredNotes
x-api-keyyesYour API key
Idempotency-Keyyes on live keysUnique per request attempt. Omitting it on a live key returns 422
X-Correlation-IDnoPropagated to the provider, so a support question can be traced end to end

Idempotency-Key is mandatory in live mode

Send a unique key per logical request — a stable one per onboarding attempt works well.

Replaying the same key returns the original stored response without calling the registry again. It is still billed. What idempotency protects is the provider call and the consistency of the answer, not the charge: every result delivered to you is a billable result, including a replayed one.

Reuse a key with a different body and the request is rejected with 409.

Test-mode keys are exempt from supplying a key at all.

Response

Every response is wrapped in the standard envelope. The verification itself is under data:

{
  "success": true,
  "statusCode": 200,
  "message": "CAC basic verification successful",
  "data": {
    "company": {
      "rcNumber": "092932",
      "companyName": "ACME LIMITED",
      "entityType": "RC",
      "companyStatus": "ACTIVE",
      "companyAddress": "13 SOME STREET, GARKI",
      "branchAddress": null,
      "state": "FCT",
      "city": "ABUJA",
      "lga": "AMAC",
      "registrationDate": "2009-04-17",
      "emailAddress": null,
      "searchScore": "1.0",
      "companyId": null
    },
    "verificationStatus": "VERIFIED",
    "rcNumber": "092932",
    "companyType": "RC",
    "referenceId": "…",
    "verificationId": "…",
    "charged": true
  },
  "timestamp": "2026-09-01T09:14:22.401Z"
}

companyId is a provider-side record id and is returned by Advance only — it is null on Basic.

Advance returns an array of companies, each with its own nested directors, plus a matchCount. One registration number can belong to more than one entity, so there is no single company field on this tier:

{
  "data": {
    "matchCount": 1,
    "companies": [
      {
        "companyId": 88213,
        "rcNumber": "092932",
        "companyName": "ACME LIMITED",
        "entityType": "RC",
        "companyStatus": "ACTIVE",
        "companyAddress": "13 SOME STREET, GARKI",
        "state": "FCT",
        "city": "ABUJA",
        "lga": "AMAC",
        "registrationDate": "2009-04-17",
        "directors": [
          {
            "id": 4821,
            "surname": "OKAFOR",
            "firstName": "CHINELO",
            "otherName": null,
            "email": null,
            "phoneNumber": null,
            "gender": "female",
            "dateOfBirth": "1978-03-02",
            "dateOfAppointment": "2009-04-17",
            "status": "ACTIVE",
            "occupation": "BUSINESSWOMAN",
            "nationality": "NIGERIAN",
            "address": "13 SOME STREET, GARKI",
            "city": "ABUJA",
            "state": "FCT",
            "lga": "AMAC",
            "identityNumber": null,
            "numSharesAlloted": "500000",
            "typeOfShares": "ORDINARY",
            "isChairman": false,
            "isDesignated": true,
            "isCorporate": false,
            "affiliateType": "DIRECTOR"
          }
        ]
      }
    ],
    "verificationStatus": "VERIFIED",
    "charged": true
  }
}

Read matchCount before trusting companies[0]

matchCount greater than one means the registration number is not unique — several distinct entities matched. Treating companies[0] as the answer in that case picks an arbitrary company.

Supply companyName to narrow the match, or surface the choice to whoever is doing the onboarding. Do not resolve it silently.

Every director field is nullable. The registry's completeness varies by company and by how long ago the filing was made — treat a null as "the registry does not hold this", not as an error.

affiliateType is the role as recorded by the registry. It is not always DIRECTOR — company secretaries and lawyers appear in the same array, so filter on it if you only want directors.

Envelope fields

FieldMeaning
verificationStatusVERIFIED when the registry returned a record
verificationIdTruztcube's id for this verification. Quote it in support requests
referenceIdThe provider's own reference
chargedWhether this call was billed
chargeAmountWhat this call cost, in naira (major units). Your rate is set by your access level — see your agreement

What is stored

What we keep, and for how long

Not kept: the raw provider payload we store for audit has the directors array stripped out entirely, so directors never reach the verification record.

Kept for up to 24 hours: the response we send you is also stored against your Idempotency-Key, so a replay can return the identical answer. During that window the complete director array — including dates of birth and contact details — sits in our cache, and a redacted copy is written durably. The redacted copy drops dateOfBirth, email, address, gender and nationality, and masks identityNumber and phoneNumber, but retains director names, occupation, city, state, lga, dateOfAppointment and shareholding.

After that window we hold no director personal data beyond the redacted record above, and we cannot reproduce a past Advance response in full.

If you need director data for longer than your own request-retry window, store it on your side under your own lawful basis and retention period. Do not treat our idempotency cache as storage.

Errors and billing

The rule is simple: you are charged when the registry answers, whatever it says. A definitive "no such company" is a search that was performed. You are not charged when the failure is ours or the provider's.

StatusMeaningCharged
200Company found
404Registry has no record for this number — including a mistyped one
202A request with this Idempotency-Key is still in flight. Retry shortly
400Missing or non-string field, or companyType / purpose outside the allowed values
402Insufficient wallet balance
403Your organisation is not subscribed to this tier
409Idempotency-Key reused with a different body
422Idempotency-Key missing on a live key
429Rate limited — by the provider, by this endpoint's concurrency limit, or by a wallet velocity guard
502 503 504Provider unavailable, overloaded or timed out

A 404 costing money is the one people query, so it is worth stating plainly: the registry was searched and returned a definitive answer. That answer has value — it tells you the company does not exist — and the provider bills us for the search either way.

This is why the leading zero matters. rcNumber is not format-checked, so 92932 is a valid request. It reaches the registry, finds nothing, and returns a billable 404. A typo costs the same as a real search.

Retry 5xx, do not retry 4xx

502, 503 and 504 mean the provider was unavailable and you were not charged. Retry with backoff, reusing the same Idempotency-Key — you may get a 202 while the first attempt is still settling, which is also safe to retry.

Be aware of what a retry does and does not protect. A retry after a failure re-executes the lookup, and if it then succeeds you are charged once, correctly. A retry after a success whose response you lost returns the stored answer and is charged again. So a retry is safe for availability, not free.

400, 404 and 422 are definitive — retrying changes nothing, and a 404 retry is a second billable search whether you reuse the key or not.

Choosing a tier

Take Basic when you need to confirm a business exists and is active — the common KYB onboarding case.

Take Advance when you need to know who is behind the company: verifying a signatory's authority, screening beneficial owners, or assessing credit. It is priced separately and returns personal data, so use it when you have a reason to hold that data, not by default.

Testing

Use a test-mode key (sk_test_…). Test mode does not require an Idempotency-Key, and CAC calls are routed under the provider's sandbox credential.

On this page