Quickstart
This quickstart takes you from nothing to a verified company case in about ten minutes, against the free sandbox. You will request access, get a token, create a company case, poll until it is ready, and read the verified result including the ownership structure.
For the concepts behind each step, see the Guide. For the full endpoint reference, see the API Reference.
Before you start
You need sandbox credentials: a client_id and a client_secret. If you do not have them, request access. You sign the Sandbox Testing Agreement and receive credentials on screen and by email.
The sandbox base URL is https://api.knowyourcustomer.dev. Everything in this quickstart is free and uses synthetic and public-record data.
Step 1: Get a token
Authentication uses OAuth2 client-credentials. Exchange your client_id and client_secret for a bearer token with scope PublicApi. The token lasts about ten minutes; send it on every request as Authorization: Bearer <token>.
# Exchange client credentials for a bearer token (~10 min TTL)
TOKEN=$(curl -fsS -X POST "$BASE_URL/connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" \
-d "scope=PublicApi" | jq -r '.access_token')
# Send it on every request:
# -H "Authorization: Bearer $TOKEN"import requests
BASE_URL = "https://api.knowyourcustomer.dev" # free Sandbox
resp = requests.post(
f"{BASE_URL}/connect/token",
data={
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"scope": "PublicApi",
},
timeout=30,
)
resp.raise_for_status()
token = resp.json()["access_token"] # ~10 min TTL
headers = {"Authorization": f"Bearer {token}"}const BASE_URL = "https://api.knowyourcustomer.dev"; // free Sandbox
const body = new URLSearchParams({
grant_type: "client_credentials",
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
scope: "PublicApi",
});
const res = await fetch(`${BASE_URL}/connect/token`, { method: "POST", body });
if (!res.ok) throw new Error(`token failed: ${res.status}`);
const { access_token } = await res.json(); // ~10 min TTL
const headers = { Authorization: `Bearer ${access_token}` };A successful response contains an access_token. Keep it for the next calls.
Step 2: Search for a company
Find the exact registry record so you can create the case against it. Search by name or registration number, optionally narrowing by country.
Try CROPWELL BISHOP CREAMERY LIMITED, a good multi-level ownership example: search cropwell bishop in GB. You can also try SC ENGINEERING PRIVATE LIMITED (SG, UEN 200815219G) or Ubizense Limited (HK, BR 69293323).
POST /v2/Companies/search
# Search the registry; pick the exact result.
curl -fsS -X POST "$BASE_URL/v2/Companies/search" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"codeiso31662":"GB","query":"CROPWELL BISHOP"}' \
| jq '.companySearch.searchResults[0]'
# -> note the exact .rawname and the registration number (00364890)r = requests.post(
f"{BASE_URL}/v2/Companies/search",
headers=headers,
json={"codeiso31662": "GB", "query": "CROPWELL BISHOP"},
timeout=30,
)
r.raise_for_status()
results = r.json()["companySearch"]["searchResults"]
rawname = results[0]["rawname"] # exact name to create the case withconst search = await fetch(`${BASE_URL}/v2/Companies/search`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ codeiso31662: "GB", query: "CROPWELL BISHOP" }),
}).then((r) => r.json());
const rawname: string = search.companySearch.searchResults[0].rawname;
// exact name to create the case withPick your result and note its exact name and registration number (00364890 for Cropwell Bishop).
Step 3: Create the case
Create a company case using the name that matches your search result. Supply the country and registration number for an exact registry match.
POST /v2/Companies with rawname, codeiso31662, and externalCode.
# Create the company case using the exact rawname from search.
CASE_ID=$(curl -fsS -X POST "$BASE_URL/v2/Companies" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"rawname\":\"$RAWNAME\",\"codeiso31662\":\"GB\",\"externalCode\":\"00364890\"}" \
| jq -r '.caseDetail.details.common.caseCommonId')
echo "caseCommonId: $CASE_ID" # the case now builds in the backgroundr = requests.post(
f"{BASE_URL}/v2/Companies",
headers=headers,
json={"rawname": rawname, "codeiso31662": "GB", "externalCode": "00364890"},
timeout=30,
)
r.raise_for_status()
case_id = r.json()["caseDetail"]["details"]["common"]["caseCommonId"]
print("caseCommonId:", case_id) # the case now builds in the backgroundconst created = await fetch(`${BASE_URL}/v2/Companies`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ rawname, codeiso31662: "GB", externalCode: "00364890" }),
}).then((r) => r.json());
const caseId = created.caseDetail.details.common.caseCommonId;
console.log("caseCommonId:", caseId); // the case now builds in the backgroundThe response returns a caseCommonId. The case now builds in the background.
Step 4: Poll until ready
The build is asynchronous. Poll the case and read its status until it is 3 (Ready). Poll every few seconds with a backoff; some jurisdictions take minutes.
GET /v2/Companies/{caseCommonId}
# Poll until status is 3 (Ready). Some jurisdictions take minutes.
for i in $(seq 1 60); do
STATUS=$(curl -fsS "$BASE_URL/v2/Companies/$CASE_ID" \
-H "Authorization: Bearer $TOKEN" \
| jq -r '.caseDetail.details.common.statusId')
echo "statusId=$STATUS"
if [ "$STATUS" = "3" ]; then break; fi
sleep 5
doneimport time
for _ in range(60):
r = requests.get(f"{BASE_URL}/v2/Companies/{case_id}", headers=headers, timeout=30)
r.raise_for_status()
status = r.json()["caseDetail"]["details"]["common"]["statusId"]
print("statusId=", status)
if status == 3: # Ready
break
time.sleep(5) # poll with a backoff; do not tight-loopconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
for (let i = 0; i < 60; i++) {
const c = await fetch(`${BASE_URL}/v2/Companies/${caseId}`, { headers })
.then((r) => r.json());
const status = c.caseDetail.details.common.statusId;
console.log("statusId=", status);
if (status === 3) break; // Ready
await sleep(5000); // poll with a backoff; do not tight-loop
}Keep polling while the status is anything other than 3. The status moves through 0 -> 50 -> 51 -> a varying subset of {53, 54, 9, 100, 107} -> 3.
Step 5: Read the verified result
Once the status is 3, read the case. You get the verified company properties (name, registration number, jurisdiction) and the ownership data: controllingEntitiesAndIndividuals and the recursive org-chart (shareholders nested by memberType).
# Read members + the recursive org-chart (the ownership tree).
curl -fsS "$BASE_URL/v2/Companies/$CASE_ID/members" \
-H "Authorization: Bearer $TOKEN" \
| jq '{controlling: (.controllingEntitiesAndIndividuals | length),
shareholders: (.shareholdersAndBeneficialOwners | length)}'
curl -fsS "$BASE_URL/v2/Companies/$CASE_ID/org-chart" \
-H "Authorization: Bearer $TOKEN" \
| jq '{root: .name, shareholders: [.shareholders[]?.name]}'
# Each individual member has its own caseCommonId, addressable at
# GET /v2/Individuals/{caseCommonId}members = requests.get(
f"{BASE_URL}/v2/Companies/{case_id}/members", headers=headers, timeout=30).json()
controlling = members.get("controllingEntitiesAndIndividuals", [])
org = requests.get(
f"{BASE_URL}/v2/Companies/{case_id}/org-chart", headers=headers, timeout=30).json()
def walk(node, depth=0):
print(" " * depth + f"- {node.get('name')} ({node.get('effectivePercentage')}%)")
for child in (node.get("shareholders") or []):
walk(child, depth + 1)
walk(org) # recurse the multi-level tree; memberType is "Company" or "Individual"const members = await fetch(`${BASE_URL}/v2/Companies/${caseId}/members`, { headers })
.then((r) => r.json());
const controlling = members.controllingEntitiesAndIndividuals ?? [];
const org = await fetch(`${BASE_URL}/v2/Companies/${caseId}/org-chart`, { headers })
.then((r) => r.json());
const walk = (node: any, depth = 0): void => {
console.log(`${" ".repeat(depth)}- ${node.name} (${node.effectivePercentage}%)`);
for (const child of node.shareholders ?? []) walk(child, depth + 1);
};
walk(org); // recurse the multi-level tree; memberType is "Company" or "Individual"For Cropwell Bishop you will see a multi-level tree: a corporate parent above individual owners. Walk the shareholders arrays to build the full structure, and read each individual member's caseCommonId to address them at /v2/Individuals/{caseCommonId}.
What next
- Verify a company, end to end: the full walkthrough with depth.
- Beneficial ownership and individuals: walk the org-chart, handle thresholds, verify individuals.
- Documents: collect and prevalidate identity and registry documents.
- AML and ongoing monitoring: screening, alerts, and review dates.
- API Reference: every endpoint, parameter, and response.
