KYB — company applicants and their UBOs¶
Five endpoints make up the partner-side KYB surface. They all key off
the parent company's externalUserId.
Creating the company applicant¶
POST /v1/applicants accepts the full KYB identity block alongside the
individual-side fields. Send type: "company" + everything you already
have on file; the backend silently drops fields the level's company_data
step doesn't enable, and the SDK collects the rest from the user.
curl -X POST 'https://api.compliance.example/v1/applicants' \
-H 'X-Api-Key: pk_live_...' -H 'X-Api-Secret: ...' \
-H 'X-Environment: production' \
-H 'Content-Type: application/json' \
-d '{
"externalUserId": "acme-corp",
"type": "company",
"verificationLevelName": "KYB_STANDARD",
"companyName": "ACME LLC",
"registrationNumber": "12345/789",
"legalForm": "LLC",
"incorporationDate": "2018-04-02",
"registeredAddress": "1 Market St, San Francisco, CA",
"country": "USA",
"email": "info@acme.com",
"phone": "+1 555 0199"
}'
email and phone on a company applicant are the company contact
(general inbox / switchboard). The contact person's own personal email
and phone are collected when they're spawned into their own KYC
sub-applicant via spawn-kyc.
To know which fields a level actually collects (and which are required),
read GET /v1/levels/{levelName} — its
companyDataFields / companyRequiredFields / companyRequiredDocuments
arrays tell you exactly what to prefill.
Heads-up — partner-claimed fields are locked. Every value you commit on create becomes immutable from the SDK side: the corresponding input renders as disabled, and
POST /api/v1/sdk/personal-inforefuses to mutate it. Reopen a claim by PATCHing the field back via the public API. See the Field lock section on the Verification Levels guide.
Reading the company¶
GET /v1/applicants/{externalUserId}/company-info¶
Structured KYB identity block (no UBOs — those have their own surface). 400 on individual applicants.
{
"externalUserId": "acme-corp",
"companyName": "ACME LLC",
"registrationNumber": "12345/789",
"legalForm": "llc",
"incorporationDate": "2018-04-02",
"registeredAddress": "1 Market St, San Francisco, CA",
"country": "USA",
"status": "active",
"riskLevel": "low",
"verificationLevelName": "KYB_STANDARD",
"createdAtUtc": "...",
"updatedAtUtc": "..."
}
GET /v1/applicants/{externalUserId}/company-structure¶
Rollup of the UBO graph — the four negative-declaration flags + per- role counts + spawned-child fan-in summary. Enough to render "this KYB is waiting on N people" without listing each UBO.
{
"externalUserId": "acme-corp",
"hasNoUbos": false,
"hasNoShareholders": false,
"hasNoDirectors": false,
"hasNoRepresentatives": false,
"counts": {
"total": 3,
"ubos": 2,
"directors": 2,
"shareholders": 0,
"representatives": 0,
"spawned": 3,
"completed": 2,
"rejected": 0,
"pending": 1
},
"allChildrenComplete": false,
"lastChildCompletedAtUtc": "2026-06-07T11:02:00Z"
}
Note: per-role counts honour the multi-role merge — one row with
roles=["ubo","director"] counts in both buckets.
Editing the company¶
PATCH /v1/applicants/{externalUserId}/company-info¶
Partial update of the KYB identity fields. Send only what you want to change.
country is rejected with 409 while a verification is in flight —
level applicability is country-gated, so a silent swap could route
the applicant into a level they're not eligible for. Reset the
applicant first if the change is genuinely needed.
curl -X PATCH 'https://api.compliance.example/v1/applicants/acme-corp/company-info' \
-H 'X-Api-Key: pk_live_...' -H 'X-Api-Secret: ...' \
-H 'X-Environment: production' \
-H 'Content-Type: application/json' \
-d '{ "legalForm": "ltd", "registeredAddress": "10 Downing St, London" }'
Editing one UBO row¶
PATCH /v1/applicants/{externalUserId}/ubos/{uboIndex}¶
uboIndex is the zero-based position in the parent's ubos[] array.
Locked fields silently ignored: linkedApplicantId, source,
verificationLinkUrl, verificationLinkExpiresAtUtc.
fullName / dateOfBirth edits on a row that's already been spawned
return identityChangedAfterSpawn=true so your operator side can
audit the rename if needed.
curl -X PATCH 'https://api.compliance.example/v1/applicants/acme-corp/ubos/0' \
-H 'X-Api-Key: pk_live_...' -H 'X-Api-Secret: ...' \
-H 'X-Environment: production' \
-H 'Content-Type: application/json' \
-d '{ "ownershipPercent": 51, "roles": ["ubo", "director"] }'
Bringing a UBO to verification¶
POST /v1/applicants/{externalUserId}/ubos/{uboIndex}/spawn-kyc¶
Creates a child applicant for the UBO at uboIndex AND mints a fresh
SDK short-link in one call. Idempotent on linkedApplicantId — second
call returns alreadyLinked: true with the same child id and a
freshly minted link.
curl -X POST 'https://api.compliance.example/v1/applicants/acme-corp/ubos/0/spawn-kyc' \
-H 'X-Api-Key: pk_live_...' -H 'X-Api-Secret: ...' \
-H 'X-Environment: production' \
-H 'Content-Type: application/json' \
-d '{ "ttlDays": 14, "lang": "ru", "theme": "dark", "returnUrl": "https://acme.com/ubo-done" }'
{
"uboIndex": 0,
"childApplicantId": "0a1b2c3d-...",
"childExternalUserId": "app_a7c4f9e1b3",
"alreadyLinked": false,
"sdkLink": {
"url": "https://sdk.compliance.example/l/x9k2m4",
"shortCode": "x9k2m4",
"expiresAtUtc": "2026-06-21T...",
"token": "eyJhbGciOi..."
}
}
You email sdkLink.url to the UBO. They complete KYC, the child
applicant's webhook fires, KybParentFanIn closes the parent KYB
when every child reaches a terminal state.
Rate-limited per credential — PartnerApi:SpawnRateLimitPerHour
(default 1000/hr). 429 with Retry-After on overage.
POST /v1/applicants/{externalUserId}/ubos/{uboIndex}/regenerate-link¶
The UBO's link expired, or they got soft-rejected and need to retry — mint a fresh link without spawning anything new. Also clears the child enrollment's rejected/failed state.
Capped at 5 regenerations per UBO row per 24 hours (configurable
via PartnerApi:RegenerateLinkPerChildCapPer24h). 429 with
Retry-After on overage.
curl -X POST 'https://api.compliance.example/v1/applicants/acme-corp/ubos/0/regenerate-link' \
-H 'X-Api-Key: pk_live_...' -H 'X-Api-Secret: ...' \
-H 'X-Environment: production' \
-H 'Content-Type: application/json' \
-d '{ "ttlDays": 30, "returnUrl": "https://acme.com/done" }'
Spawn-KYC vs Regenerate-link cheat-sheet¶
| You want to... | Call |
|---|---|
| First-time KYC on a UBO row | spawn-kyc |
| Retry an already-spawned UBO | regenerate-link |
| See if a UBO is done | company-structure → counts block |
| Edit a UBO row (typo, role change) | PATCH /ubos/{uboIndex} |
| Update the company itself | PATCH /company-info |