curl -X POST https://verification.didit.me/v3/kyb/ubo/ \
-H 'x-api-key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"kyb_response_id": "69aeeb95febb0f1704042259",
"vendor_data": "company-1234"
}'import os, time, requests
search = requests.post(
"https://verification.didit.me/v3/kyb/search/",
headers={"x-api-key": os.environ["DIDIT_API_KEY"]},
json={"country_code": "GB", "name": "Tesco"},
timeout=60,
).json()
kyb_response_id = search["kyb_registry"]["companies"][0]["kyb_response_id"]
resp = requests.post(
"https://verification.didit.me/v3/kyb/ubo/",
headers={"x-api-key": os.environ["DIDIT_API_KEY"]},
json={"kyb_response_id": kyb_response_id, "vendor_data": "company-1234"},
timeout=90,
)
resp.raise_for_status()
data = resp.json()
request_id = data["request_id"]
print("Keep this request ID:", request_id)
company = data["kyb_registry"]
if resp.status_code == 202 or company.get("fetch_status") == "pending":
# This client wait limit does not cancel the report or create a new purchase.
deadline = time.monotonic() + 300
while company.get("fetch_status") != "resolved" and time.monotonic() < deadline:
time.sleep(5)
decision = requests.get(
f"https://verification.didit.me/v3/session/{request_id}/decision/",
headers={"x-api-key": os.environ["DIDIT_API_KEY"]},
timeout=30,
)
decision.raise_for_status()
checks = decision.json().get("registry_checks") or []
if checks:
company = checks[0].get("company") or company
if company.get("fetch_status") == "resolved":
print(company["company_name"], company["registry_tier_delivered"])
else:
print("Still processing; retrieve this request later:", request_id)const res = await fetch('https://verification.didit.me/v3/kyb/ubo/', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
kyb_response_id: '69aeeb95febb0f1704042259',
vendor_data: 'company-1234',
}),
});
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
const data = await res.json();
const requestId = data.request_id;
console.log('Keep this request ID:', requestId);
let company = data.kyb_registry;
if (res.status === 202 || company.fetch_status === 'pending') {
// This client wait limit does not cancel the report or create a new purchase.
const deadline = Date.now() + 300000;
while (company.fetch_status !== 'resolved' && Date.now() < deadline) {
await new Promise(resolve => setTimeout(resolve, 5000));
const decisionResponse = await fetch(
`https://verification.didit.me/v3/session/${requestId}/decision/`,
{ headers: { 'x-api-key': 'YOUR_API_KEY' }, signal: AbortSignal.timeout(30000) },
);
if (!decisionResponse.ok) throw new Error(`Retrieval failed: ${decisionResponse.status}`);
const decision = await decisionResponse.json();
company = decision.registry_checks?.[0]?.company ?? company;
}
}
if (company.fetch_status === 'resolved') {
console.log(company.company_name, company.registry_tier_delivered);
} else {
console.log('Still processing; retrieve this request later:', requestId);
}<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://verification.didit.me/v3/kyb/ubo/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'kyb_response_id' => '69aeeb95febb0f1704042259',
'vendor_data' => '<string>',
'metadata' => [
],
'save_api_request' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://verification.didit.me/v3/kyb/ubo/"
payload := strings.NewReader("{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"<string>\",\n \"metadata\": {},\n \"save_api_request\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://verification.didit.me/v3/kyb/ubo/")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"<string>\",\n \"metadata\": {},\n \"save_api_request\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://verification.didit.me/v3/kyb/ubo/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"<string>\",\n \"metadata\": {},\n \"save_api_request\": true\n}"
response = http.request(request)
puts response.read_body{
"request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e",
"kyb_registry": {
"uuid": "f1e2d3c4-b5a6-4789-9c0d-1e2f3a4b5c6d",
"node_id": "feature_kyb_registry",
"status": "In Review",
"registry_status": "active",
"data_resolved": true,
"company_name": "Tesco PLC",
"registration_number": "00445790",
"country_code": "GB",
"region": "",
"company_type": "Public Limited Company",
"incorporation_date": "1947-11-27",
"registered_address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA",
"tax_number": "GB412 8925 93",
"risk_level": "low risk",
"verification_status": "verified",
"is_from_registry": true,
"fetch_status": "resolved",
"alternative_names": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY",
"nature_of_business": null,
"registered_capital": null,
"registered_capital_amount": null,
"registered_capital_currency": null,
"website": "https://www.tesco.com/",
"email": "customer.service@tesco.co.uk",
"phone": "03301231688",
"legal_entity_identifier": null,
"location_of_registration": null,
"financial_summary": {
"currency": "Pound (£)",
"total_number_of_shares": "8174378721",
"meta_detail": {
"Class_Of_Share": "Ordinary"
}
},
"officers": [
{
"uuid": "a1b2c3d4-e5f6-4789-9a0b-1c2d3e4f5a6b",
"name": "CHRISTOPHER JON TAYLOR",
"designation": "Secretary",
"role": "secretary",
"nationality": null,
"is_active": true,
"kyc_status": null,
"kyc_session_url": null
},
{
"uuid": "b2c3d4e5-f6a7-4890-8b1c-2d3e4f5a6b7c",
"name": "MELISSA BETHELL",
"designation": "Director",
"role": "director",
"nationality": "British",
"is_active": true,
"kyc_status": null,
"kyc_session_url": null
}
],
"beneficial_owners": [],
"addresses": [
{
"address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA",
"type": "legal_entity_registry_address",
"description": "registered office address"
}
],
"industries": null,
"accounts": {
"last_account": "26 February 2025",
"next_account": "26 February 2026",
"due_by_date": "26 August 2026"
},
"registry_data": {
"name": "Tesco PLC",
"registration_number": "00445790",
"status": "active",
"type": "Public Limited Company",
"incorporation_date": "1947-11-27",
"tax_number": "GB412 8925 93",
"country_code": "GB",
"fetch_status": "resolved",
"industries_detail": [
{
"code": "47110",
"name": "Retail Sale In Non-specialised Stores With Food, Beverages Or Tobacco Predominating",
"description": null,
"meta_detail": []
}
]
},
"user_provided_data": null,
"confirmed_by_user_at": null,
"last_console_edit_at": null,
"is_editable": true
},
"vendor_data": "company-1234",
"metadata": null,
"created_at": "2026-06-11T10:45:00.000000+00:00"
}KYB Registry UBOs
Retrieve the Lite profile, the direct shareholders and the ultimate beneficial owners resolved through the ownership chain (registry_data.ubo_resolution carries the corporate parent chain) of one candidate returned by POST /v3/kyb/search/.
Pairing with search. Pass the candidate’s kyb_response_id exactly as returned by a search of the same application. The search runs on the Lite registry chain; when the country’s ownership data lives at another registry Didit re-finds the company there by registration number, at no extra cost.
Billing. Billed at the country’s UBO price - see KYB registry pricing. The tier delivered is the highest the country and the registry can serve: Shareholders when the resolved report names no natural person as a beneficial owner, Lite when there is no ownership record at all; registry_tier_delivered names what was billed. The balance is checked against the UBO price before any registry call (403 when short).
Availability. v3 only. Every call creates a business session (its id is the request_id).
Pending reports. When a report is still being prepared, or the ownership section has not been retrieved from the registry yet, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, kyb_registry.registry_ownership_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session//decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.
curl -X POST https://verification.didit.me/v3/kyb/ubo/ \
-H 'x-api-key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"kyb_response_id": "69aeeb95febb0f1704042259",
"vendor_data": "company-1234"
}'import os, time, requests
search = requests.post(
"https://verification.didit.me/v3/kyb/search/",
headers={"x-api-key": os.environ["DIDIT_API_KEY"]},
json={"country_code": "GB", "name": "Tesco"},
timeout=60,
).json()
kyb_response_id = search["kyb_registry"]["companies"][0]["kyb_response_id"]
resp = requests.post(
"https://verification.didit.me/v3/kyb/ubo/",
headers={"x-api-key": os.environ["DIDIT_API_KEY"]},
json={"kyb_response_id": kyb_response_id, "vendor_data": "company-1234"},
timeout=90,
)
resp.raise_for_status()
data = resp.json()
request_id = data["request_id"]
print("Keep this request ID:", request_id)
company = data["kyb_registry"]
if resp.status_code == 202 or company.get("fetch_status") == "pending":
# This client wait limit does not cancel the report or create a new purchase.
deadline = time.monotonic() + 300
while company.get("fetch_status") != "resolved" and time.monotonic() < deadline:
time.sleep(5)
decision = requests.get(
f"https://verification.didit.me/v3/session/{request_id}/decision/",
headers={"x-api-key": os.environ["DIDIT_API_KEY"]},
timeout=30,
)
decision.raise_for_status()
checks = decision.json().get("registry_checks") or []
if checks:
company = checks[0].get("company") or company
if company.get("fetch_status") == "resolved":
print(company["company_name"], company["registry_tier_delivered"])
else:
print("Still processing; retrieve this request later:", request_id)const res = await fetch('https://verification.didit.me/v3/kyb/ubo/', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
kyb_response_id: '69aeeb95febb0f1704042259',
vendor_data: 'company-1234',
}),
});
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
const data = await res.json();
const requestId = data.request_id;
console.log('Keep this request ID:', requestId);
let company = data.kyb_registry;
if (res.status === 202 || company.fetch_status === 'pending') {
// This client wait limit does not cancel the report or create a new purchase.
const deadline = Date.now() + 300000;
while (company.fetch_status !== 'resolved' && Date.now() < deadline) {
await new Promise(resolve => setTimeout(resolve, 5000));
const decisionResponse = await fetch(
`https://verification.didit.me/v3/session/${requestId}/decision/`,
{ headers: { 'x-api-key': 'YOUR_API_KEY' }, signal: AbortSignal.timeout(30000) },
);
if (!decisionResponse.ok) throw new Error(`Retrieval failed: ${decisionResponse.status}`);
const decision = await decisionResponse.json();
company = decision.registry_checks?.[0]?.company ?? company;
}
}
if (company.fetch_status === 'resolved') {
console.log(company.company_name, company.registry_tier_delivered);
} else {
console.log('Still processing; retrieve this request later:', requestId);
}<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://verification.didit.me/v3/kyb/ubo/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'kyb_response_id' => '69aeeb95febb0f1704042259',
'vendor_data' => '<string>',
'metadata' => [
],
'save_api_request' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://verification.didit.me/v3/kyb/ubo/"
payload := strings.NewReader("{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"<string>\",\n \"metadata\": {},\n \"save_api_request\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://verification.didit.me/v3/kyb/ubo/")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"<string>\",\n \"metadata\": {},\n \"save_api_request\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://verification.didit.me/v3/kyb/ubo/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"<string>\",\n \"metadata\": {},\n \"save_api_request\": true\n}"
response = http.request(request)
puts response.read_body{
"request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e",
"kyb_registry": {
"uuid": "f1e2d3c4-b5a6-4789-9c0d-1e2f3a4b5c6d",
"node_id": "feature_kyb_registry",
"status": "In Review",
"registry_status": "active",
"data_resolved": true,
"company_name": "Tesco PLC",
"registration_number": "00445790",
"country_code": "GB",
"region": "",
"company_type": "Public Limited Company",
"incorporation_date": "1947-11-27",
"registered_address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA",
"tax_number": "GB412 8925 93",
"risk_level": "low risk",
"verification_status": "verified",
"is_from_registry": true,
"fetch_status": "resolved",
"alternative_names": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY",
"nature_of_business": null,
"registered_capital": null,
"registered_capital_amount": null,
"registered_capital_currency": null,
"website": "https://www.tesco.com/",
"email": "customer.service@tesco.co.uk",
"phone": "03301231688",
"legal_entity_identifier": null,
"location_of_registration": null,
"financial_summary": {
"currency": "Pound (£)",
"total_number_of_shares": "8174378721",
"meta_detail": {
"Class_Of_Share": "Ordinary"
}
},
"officers": [
{
"uuid": "a1b2c3d4-e5f6-4789-9a0b-1c2d3e4f5a6b",
"name": "CHRISTOPHER JON TAYLOR",
"designation": "Secretary",
"role": "secretary",
"nationality": null,
"is_active": true,
"kyc_status": null,
"kyc_session_url": null
},
{
"uuid": "b2c3d4e5-f6a7-4890-8b1c-2d3e4f5a6b7c",
"name": "MELISSA BETHELL",
"designation": "Director",
"role": "director",
"nationality": "British",
"is_active": true,
"kyc_status": null,
"kyc_session_url": null
}
],
"beneficial_owners": [],
"addresses": [
{
"address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA",
"type": "legal_entity_registry_address",
"description": "registered office address"
}
],
"industries": null,
"accounts": {
"last_account": "26 February 2025",
"next_account": "26 February 2026",
"due_by_date": "26 August 2026"
},
"registry_data": {
"name": "Tesco PLC",
"registration_number": "00445790",
"status": "active",
"type": "Public Limited Company",
"incorporation_date": "1947-11-27",
"tax_number": "GB412 8925 93",
"country_code": "GB",
"fetch_status": "resolved",
"industries_detail": [
{
"code": "47110",
"name": "Retail Sale In Non-specialised Stores With Food, Beverages Or Tobacco Predominating",
"description": null,
"meta_detail": []
}
]
},
"user_provided_data": null,
"confirmed_by_user_at": null,
"last_console_edit_at": null,
"is_editable": true
},
"vendor_data": "company-1234",
"metadata": null,
"created_at": "2026-06-11T10:45:00.000000+00:00"
}POST /v3/kyb/search/ returns candidate companies when you need the ultimate beneficial owners: the shareholders plus the natural persons and corporate parents resolved through the ownership chain (registry_data.ubo_resolution).
It is billed at the country’s UBO price (per-country prices). The tier delivered is the highest the country and the registry can serve, and registry_tier_delivered names what was billed: shareholders when the report names no natural person as a beneficial owner, basic when there is no ownership record at all.
When the report is still being prepared, or the ownership section has not been retrieved from the registry yet, the endpoint returns 202 Accepted, kyb_registry.fetch_status: "pending", kyb_registry.registry_ownership_status: "pending", and registry_tier_delivered: null.
Keep the returned request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved.
Didit completes the pending report automatically and charges the delivered tier once it is ready.
Do not repeat the purchase request while it is pending: each new POST creates a separate request.
Available on v3 only. POST /v2/kyb/select/ and POST /v3/kyb/select/ keep their contract and price.Authorizations
Your application's API key, from Developers -> API keys in the Business Console. The primary key has full access. A named key can be scoped: none, read or write per resource, limited to some workflows or to approved sessions, to a list of IP addresses, and to an expiry date. 401 means the key is missing, wrong, revoked or expired; 403 means the key has no access to this resource or action, or the request came from an address outside its IP list; 404 on a session route means the session is outside the key's workflows or statuses. A key without media access receives image, video and PDF URLs as null, and a key without sessions write receives session links and tokens as null. See https://docs.didit.me/console/api-keys.
Body
Candidate handle from POST /v3/kyb/search/ (kyb_registry.companies[].kyb_response_id). Ephemeral — use it shortly after the search that produced it.
255"69aeeb95febb0f1704042259"
Your identifier for this company; echoed back and stored with the business session.
Free-form JSON stored with the business session and echoed back.
Must be true (the default). KYB registry selects are always persisted so billing and retrieval can be tracked; false returns 400.
Response
Company report returned and billed at the delivered tier. Ownership reports still being prepared can return 202; keep request_id to retrieve their saved decision.
Id of the business session created for this retrieval. The stored record (visible in the console) keeps updating if the registry resolves after the response.
The tier this endpoint sells: basic (Lite) for select, shareholders, ubo.
basic, shareholders, ubo The tier actually delivered and billed. Lower than requested when the country's registries hold no ownership data, when the registry has no ownership record for this company, or when the resolved report of a ubo request names no natural person as a beneficial owner.
basic, shareholders, ubo, null Full registry company profile.
Show child attributes
Show child attributes
Echo of the vendor_data you sent.
Echo of the metadata you sent.