Skip to content

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-info refuses 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" }'
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-structurecounts block
Edit a UBO row (typo, role change) PATCH /ubos/{uboIndex}
Update the company itself PATCH /company-info