API documentation
Search companies, investigate ownership and retrieve saved evidence. Start with authentication, then make your first request.
From company search to ownership evidence.
Use one Zavia key for REST and MCP. Both work with the same saved investigations and allowance as your dashboard. Ownership reports require paid credit; free company previews do not include owners or directors.
https://zavia-api.buldumak.workers.dev/api/v1The OpenAPI 3.1 download includes complete JSON examples for every endpoint, investigation start/resume/cancel, free and paid usage, and documented errors. Import it into your API client to inspect requests and schemas. All example companies, people, IDs and results are fictional. Replace them with your own inputs and IDs returned by search.
Check your connection first
This returns your workspace and remaining allowance without creating a report.
curl "https://zavia-api.buldumak.workers.dev/api/v1/usage" \
-H "Authorization: Bearer $ZAVIA_API_KEY"Authentication and keys
Send Authorization: Bearer YOUR_ZAVIA_KEY on every data request. Keep keys on your server or in your client’s secret store.
- The workspace owner can create up to five active keys, named for each integration.
- Choose 30, 90 or 365 days. The full secret is shown only once; it cannot be recovered.
- Read-only keys can search, list saved reports, retrieve evidence and check usage. Full access also starts, resumes or cancels investigations.
- Revoking a key stops new requests immediately. Create a replacement, update your app, test it, then revoke the old key.
- Changing a plan changes the key’s available allowance. Creating more keys does not add credits.
Available data and endpoint map
Company and ownership evidence comes through the Global Database KYB API. Your application calls Zavia with a Zavia key; the upstream credential stays on our server. Zavia adds workspace permissions, investigation runs, saved snapshots, usage accounting and display calculations.
| Data | Call | Returned fields |
|---|---|---|
| Company identity | POST /search | candidates[]: company ID, legal name, registration/VAT number, country, status, address and available identity details. |
| Beneficial owners and control | POST /investigations | ubo.data[]: effective, direct and indirect interest, control types, supporting paths and provenance. |
| Direct shareholders | POST /investigations | shareholders[]: holder, type, percentage, current/historical status, source and evidence flags. |
| Senior-officer candidates | POST /investigations | officers: relevant appointments for control review, sources, availability and excludedCount. Filtered to at most 200 candidates; not a complete director register. |
| Corporate structure and gaps | POST /investigations | group, corporateBranches, ubo.terminal_entities, warnings and unresolvedGaps. Group context does not establish an ownership percentage. |
| Saved evidence | GET /cases → GET /cases/{caseId} | List case IDs, then read the complete saved report in report. A saved snapshot does not automatically refresh upstream data. |
| Allowance and connection | GET /usage | Workspace, report credits, search allowance, request rate and reset times. |
Shareholders, officer candidates and group context are bundled into the investigation response. There are no separate Zavia endpoints for those datasets. Start with search, confirm the legal entity, then use its exact ID to request the report.
Coverage and source limitations
Availability varies by company and jurisdiction. Optional profile sections may be unavailable while the UBO result remains usable; check their status and the report warnings. Empty fields and unknown percentages must remain unknown. Sources may identify an official registry, modelled data, enrichment or an unresolved source. A calculation is derived evidence, not a registry filing.
Owner identity fields can include first and last name, nationality, birth month/year and locality/postal code/country when supplied. Birth day and street address are not returned in this owner block. Reserved fields such as is_pep and ownership.voting_percentage currently return null; they do not establish a negative screening result or absence of voting control.
Registry names, filing dates and document references are only usable when supplied in the evidence. A retrieval timestamp is not a filing date. The API does not guarantee an official document for every ownership link.
Outside this API
Zavia does not expose the entire upstream catalogue. Financial statements, credit scores, sales contacts, sanctions or PEP screening, registry-document listing/downloads, monitoring management, PDF/CSV export and analyst casework updates have no public REST or MCP operation here. A source URL is a reference, not a document-download endpoint. API investigations use the provider’s internal ownership dataset; paid live-registry enrichment is not a public request option.
The original finding is ubo. ownershipPaths, ultimateBeneficialOwners and calculation are display views; do not use them to overwrite provider totals or omit control-based owners. An optional ownershipExploration is a separate canvas snapshot. Sample reports carry isSample: true and must not be used as real-company evidence. Legacy publicResearch, if present in an older saved case, is supplementary public-web material, separate from the provider evidence.
Search for the legal entity
/searchProvide a company name, registration number or VAT number plus a two-letter country code. Use subdivision for a US state or Canadian jurisdiction when needed.
curl "https://zavia-api.buldumak.workers.dev/api/v1/search" \
-H "Authorization: Bearer $ZAVIA_API_KEY" \
-H "Content-Type: application/json" \
--data "{\"identifier\":\"Global Data Intelligence Limited\",\"country\":\"GB\"}"The response contains candidates with id, name, countryCode, registrationNumber and available company details. Retain the exact ID of the confirmed company. Never assume the first fuzzy match is correct.
{
"status": "ok",
"requestId": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
"query": {
"identifier": "EXAMPLE LIMITED",
"country": "GB",
"subdivision": ""
},
"candidates": [
{
"id": "123456",
"name": "EXAMPLE LIMITED",
"countryCode": "GB",
"status": "Active",
"source": {
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
}
}
],
"selectionRequired": true,
"searchComplete": true,
"warnings": []
}import json, os, urllib.request
request = urllib.request.Request(
"https://zavia-api.buldumak.workers.dev/api/v1/search",
data=json.dumps({"identifier": "Global Data Intelligence Limited", "country": "GB"}).encode(),
headers={"Authorization": "Bearer " + os.environ["ZAVIA_API_KEY"],
"Content-Type": "application/json",
"User-Agent": "Zavia-Python/1.0"},
method="POST",
)
with urllib.request.urlopen(request, timeout=60) as response:
print(json.load(response)["candidates"]){
"status": "ok",
"requestId": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
"query": {
"identifier": "Example company",
"country": "GB",
"subdivision": ""
},
"candidates": [],
"selectionRequired": false,
"searchComplete": true,
"warnings": []
}An empty list with searchComplete: true means no match was found in this search. If searchComplete is false, results are incomplete; inspect warnings before choosing a company.
Start or resume an investigation
/investigationsUse a confirmed selectedCompanyId and a newly generated UUID runId. Replace the placeholders below. The same run ID and settings must be reused on every retry.
{
"identifier": "Global Data Intelligence Limited",
"country": "GB",
"selectedCompanyId": "ID_FROM_SEARCH",
"runId": "YOUR_UUID_V4",
"threshold": 25,
"maxDepth": 10
}| Field | Requirement |
|---|---|
identifier, country | Required; use the same company search. |
selectedCompanyId | Required; exact numeric ID returned by search. |
runId | Required; UUID. Keep it until the investigation completes. |
threshold | Optional: 25, 10, 5 or 1; defaults to 25. Ownership must be strictly above this percentage. Control-based owners can also be returned below it. |
maxDepth | Optional: 1–10. Paid reports follow up to 10 layers. |
subdivision | Optional; preserve the state or jurisdiction used in search. |
purpose | Optional: ownership (default), onboarding, supplier, group or investigation. |
resume | Set true after a processing response. |
cancel | Set true to release an unfinished reservation. |
Handle processing responses
200 returns the saved report and caseId. 202 returns status: processing, runId, retryAfterSeconds and available progress. Wait, then send the same body with resume: true.
Replaying a completed run returns the saved case without charging again. Changing a company or settings while reusing a run ID returns run_input_changed. Do not silently create a new run when a request times out.
{
"status": "processing",
"runId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"retryAfterSeconds": 3,
"progress": {
"entitiesResolved": 2,
"entitiesPending": 1,
"currentDepth": 2
},
"message": "Calculating ownership. The completed report will be saved to your workspace."
}The JavaScript example requires a UUID saved in ZAVIA_RUN_ID. Keep it with the company and settings across process restarts. It stops after 100 attempts and never treats a quota error as a request to buy more credit automatically.
// Node.js 22+. Save one UUID in ZAVIA_RUN_ID before the first request.
// Keep the same company, settings and UUID across restarts and retries.
const input = {
identifier: process.env.ZAVIA_COMPANY_NAME,
country: process.env.ZAVIA_COUNTRY,
selectedCompanyId: process.env.ZAVIA_COMPANY_ID,
runId: process.env.ZAVIA_RUN_ID,
threshold: 25,
maxDepth: 10
};
const key = process.env.ZAVIA_API_KEY;
if (!key || Object.values(input).some(value => !value)) {
throw new Error("Set the ZAVIA_* variables above before making a request.");
}
const wait = seconds => new Promise(resolve => setTimeout(resolve, seconds * 1000));
async function investigate() {
let networkFailures = 0;
for (let attempt = 0; attempt < 100; attempt++) {
let response;
try {
response = await fetch("https://zavia-api.buldumak.workers.dev/api/v1/investigations", {
method: "POST",
headers: { Authorization: "Bearer " + key, "Content-Type": "application/json" },
body: JSON.stringify(input),
signal: AbortSignal.timeout(90000)
});
} catch {
if (++networkFailures > 3) throw new Error("Connection failed. Keep runId " + input.runId + " and retry the same inputs later.");
await wait(2 ** networkFailures);
continue;
}
const body = await response.json();
if (response.status === 429 && body.error?.code === "rate_limited") {
const delay = Number(response.headers.get("Retry-After"));
await wait(Number.isFinite(delay) ? Math.max(1, Math.min(60, delay)) : 1);
continue;
}
if (response.status === 409 && body.error?.code === "run_in_progress") {
await wait(3);
continue;
}
if (!response.ok) throw new Error((body.error?.code || response.status) + ": " + (body.error?.message || "Request failed") + ". runId=" + input.runId);
if (response.status !== 202) return body;
input.resume = true;
await wait(Math.max(1, Math.min(60, Number(body.retryAfterSeconds) || 3)));
}
throw new Error("Polling limit reached. Keep runId " + input.runId + " and resume later.");
}
console.log(await investigate());Cancel or recover a run
Send the original inputs with cancel: true to release an unfinished reservation. Cancelling a completed run returns its saved report; it does not refund or delete it. A started background job can be resumed for up to 24 hours, subject to upstream availability. An initial request that never established a background job expires after five minutes.
After a connection timeout, retry the same UUID first. If the server confirms run_failed or ubo_job_expired, the run is terminal: use a new UUID for another attempt. A different UUID can create another charge, so do not generate one just because a response was lost.
Understand the evidence
The completed result includes subject, ubo, ownershipPaths, available source metadata, warnings and a billing object. Missing percentages, incomplete chains and unavailable registry documents remain explicit gaps.
Read the ownership response correctly
The OpenAPI download includes typed owner, path, provenance, summary and billing schemas. Use these fields to interpret the result:
| Field | Meaning |
|---|---|
ubo.data | Owners returned by the provider. Preserve control_types: ownership and indirect_control have different meanings. An empty array is not proof that a company has no beneficial owner. |
ubo.data[].ownership.effective_percentage | Total effective interest. Null means unknown; zero means a reported zero. |
ubo.summary.percentages_are_minimum | When true, reported interests are minimum known figures. |
ubo.summary.ubo_determination_conclusive | Whether the provider considers the ownership determination conclusive; review warnings and applicable policy. |
ubo.summary.chain_complete | Whether the returned ownership chain is complete. |
ubo.summary.determination | calculated, partial, none_identified, listed_parent, exempt_listed or not_available. |
ubo.data[].paths_total / paths_truncated | Some supporting routes may be omitted. Preserve the provider total; do not replace it with the sum of visible paths. |
ubo.data[].paths[].nodes | Source entity IDs and percentages for each link. Same-name people must not be merged without identity evidence. |
ubo.data[].provenance / nodes[].provenance | Source information when supplied. Null or absent provenance is an evidence gap; retrieval time is not a filing date. |
ubo.terminal_entities | Listed, unresolved or other branches where traversal stopped, with the reason. |
shareholders / officers / group | Supporting profile sections. Officers are filtered review candidates, not automatically designated owners. Group context is never used to calculate an interest. |
ultimateBeneficialOwners / ownershipPaths | Derived compatibility views. Use ubo.data for the complete provider finding, including unknown percentages and control-based owners. |
warnings / unresolvedGaps | Evidence limitations that must remain visible in downstream decisions. |
billing.units | Zero or one report unit. Empty paid ownership results restore credit; usable partial evidence can still consume one unit. |
{
"status": "ok",
"caseId": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
"subject": {
"id": "123456",
"name": "EXAMPLE LIMITED",
"countryCode": "GB"
},
"ubo": {
"summary": {
"threshold": 25,
"chain_complete": false,
"ubo_determination_conclusive": false,
"percentages_are_minimum": true,
"determination": "partial"
},
"data": [
{
"id": "example-person",
"full_name": "Alex Example",
"control_types": [
"ownership"
],
"ownership": {
"direct_percentage": 15,
"indirect_percentage": 22,
"effective_percentage": 37
},
"paths_total": 3,
"paths_truncated": false
}
]
},
"unresolvedGaps": [
"Untraced ownership: 30%"
],
"billing": {
"units": 1,
"reason": "ownership_evidence",
"message": "Usable partial ownership evidence was returned."
}
}This example reports a minimum known interest of 37%, assembled from direct and indirect routes. The incomplete determination still needs review. The full response also contains paths, supporting profile sections and source metadata as described in OpenAPI.
Complete investigation response example
This is a separate fictional scenario with 70% known ownership and a 30% unresolved corporate branch. No fields have been shortened with ellipses. Optional fields appear only when available in a real result.
{
"status": "ok",
"subject": {
"id": "123456",
"name": "EXAMPLE LIMITED",
"countryCode": "GB",
"status": "Active",
"source": {
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
}
},
"query": {
"identifier": "EXAMPLE LIMITED",
"country": "GB",
"subdivision": ""
},
"ubo": {
"company": {
"id": "123456",
"name": "EXAMPLE LIMITED",
"country_code": "GB",
"status": "Active"
},
"summary": {
"as_of": "2026-11-08",
"threshold": 25,
"max_depth": 10,
"max_depth_reached": 1,
"total_ubos": 1,
"determination": "partial",
"chain_complete": false,
"ubo_determination_conclusive": false,
"percentages_are_minimum": true,
"risk_flags": [],
"data_warnings": [
"Ownership beyond Example Holdings is unavailable."
],
"ownership_breakdown": {
"ubo_percentage": 70,
"below_threshold_person_percentage": 0,
"listed_entity_percentage": 0,
"unresolved_entity_percentage": 30,
"untraced_percentage": 0,
"total_percentage": 100
},
"enrichment": {
"mode": "internal",
"status": "not_requested",
"units_consumed": 0
}
},
"data": [
{
"id": "example-person",
"full_name": "Alex Example",
"first_name": "Alex",
"last_name": "Example",
"nationality": null,
"date_of_birth": null,
"address": null,
"status": null,
"ceased_on": null,
"notified_on": null,
"country_of_residence": null,
"is_pep": null,
"control_types": [
"ownership"
],
"ownership": {
"direct_percentage": 70,
"indirect_percentage": 0,
"effective_percentage": 70,
"voting_percentage": null,
"has_direct_ownership": true,
"has_indirect_ownership": false
},
"paths_total": 1,
"paths_truncated": false,
"paths": [
{
"type": "direct",
"depth": 1,
"path_percentage": 70,
"nodes": [
{
"name": "EXAMPLE LIMITED",
"type": "company",
"level": 0,
"entity_id": "123456",
"country_code": "GB"
},
{
"name": "Alex Example",
"type": "person",
"level": 1,
"entity_id": "example-person",
"percentage": 70,
"percentage_derived": false
}
]
}
],
"provenance": {
"source": null,
"source_type": null,
"determination_type": "calculated",
"retrieved_at": "2026-11-08T08:00:00Z"
}
}
],
"terminal_entities": [
{
"entity_id": "654321",
"name": "EXAMPLE HOLDINGS",
"type": "company",
"country_code": "GB",
"effective_percentage": 30,
"reason": "Ownership information unavailable",
"path": [
{
"name": "EXAMPLE LIMITED",
"type": "company",
"level": 0,
"entity_id": "123456",
"country_code": "GB"
},
{
"name": "EXAMPLE HOLDINGS",
"type": "company",
"level": 1,
"entity_id": "654321",
"country_code": "GB",
"percentage": 30,
"percentage_derived": false
}
]
}
],
"total_results": 1,
"total_pages": 1
},
"summary": {
"directShareholderCount": 2,
"currentPercentageTotal": 100,
"shareholderSetComplete": true,
"ownershipChainComplete": false,
"ownershipPathCount": 1,
"corporateBranchCount": 1
},
"shareholders": [
{
"id": "example-person",
"name": "Alex Example",
"type": "person",
"percentage": 70,
"status": "current",
"source": {
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
},
"materialSources": [
{
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
}
],
"identitySources": [],
"eligibleForOwnershipCalculation": true,
"identityEligibleForAutomation": false,
"issueCodes": [
"source_unavailable"
]
},
{
"id": "654321",
"name": "EXAMPLE HOLDINGS",
"type": "company",
"percentage": 30,
"status": "current",
"source": {
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
},
"materialSources": [
{
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
}
],
"identitySources": [],
"linkedCompanyId": "654321",
"eligibleForOwnershipCalculation": true,
"identityEligibleForAutomation": false,
"issueCodes": [
"source_unavailable"
]
}
],
"officers": {
"status": "unavailable",
"checkedAt": "2026-11-08T08:00:00Z",
"candidates": [],
"excludedCount": 0,
"message": "Officer evidence was unavailable in this fictional example."
},
"ownershipPaths": [
{
"personName": "Alex Example",
"identityBasis": "provider_person_id",
"effectivePercentage": 70,
"provisional": true,
"path": [
{
"companyId": "123456",
"companyName": "EXAMPLE LIMITED",
"companyCountryCode": "GB",
"holderName": "Alex Example",
"holderType": "person",
"edgePercentage": 70,
"effectivePercentage": 70,
"source": {
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
},
"materialSources": [
{
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
}
],
"identitySources": []
}
]
}
],
"ultimateBeneficialOwners": [
{
"personName": "Alex Example",
"identityBasis": "provider_person_id",
"effectivePercentage": 70,
"exceedsThreshold": true,
"pathCount": 1,
"source": {
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
}
}
],
"corporateBranches": [
{
"holderName": "EXAMPLE HOLDINGS",
"directPercentage": 30,
"effectivePercentage": 30,
"ownershipSource": {
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
},
"materialSources": [
{
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
}
],
"identitySources": [],
"status": "unavailable",
"issueCodes": [
"ownership_unavailable"
],
"path": [
"EXAMPLE LIMITED",
"EXAMPLE HOLDINGS"
]
}
],
"group": {
"status": "not_found",
"usedForOwnershipCalculation": false,
"path": [],
"nodes": []
},
"calculation": {
"thresholdPercentage": 25,
"denominatorTolerance": 0.5,
"provenance": {
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
}
},
"retrievalTrace": [],
"warnings": [
"Fictional example. Source provenance and part of the ownership chain are unavailable."
],
"unresolvedGaps": [
"Unresolved corporate ownership: 30%",
"Source provenance is unavailable."
],
"determinationOutcome": "incomplete",
"billing": {
"units": 1,
"reason": "ownership_evidence",
"message": "Usable partial ownership evidence was returned."
},
"isSample": false,
"previewOnly": false,
"generatedAt": "2026-11-08T08:00:00Z",
"requestId": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
"caseId": "dddddddd-dddd-4ddd-8ddd-dddddddddddd"
}Missing sources or percentages are never instructions to invent data. A successful HTTP response confirms that a report was returned, not that an ownership or compliance decision is complete.
Read saved reports
/cases?limit=20Returns summaries in cases, newest first. Use nextCursor as the next request’s cursor when present. limit accepts 1–50.
curl "https://zavia-api.buldumak.workers.dev/api/v1/cases?limit=20" \
-H "Authorization: Bearer $ZAVIA_API_KEY"{
"cases": [
{
"id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
"companyId": "123456",
"companyName": "EXAMPLE LIMITED",
"countryCode": "GB",
"status": "active",
"createdAt": 1794124800,
"updatedAt": 1794124800
}
],
"nextCursor": null
}For another page, URL-encode the returned nextCursor and send /cases?limit=20&cursor=YOUR_ENCODED_CURSOR. Stop when nextCursor is null.
/cases/:caseIdcurl "https://zavia-api.buldumak.workers.dev/api/v1/cases/YOUR_CASE_ID" \
-H "Authorization: Bearer $ZAVIA_API_KEY"Returns { "id": "...", "report": { ... } }. Reads do not use another investigation credit. A case outside your workspace returns 404.
Complete saved-report response example
{
"id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
"report": {
"status": "ok",
"subject": {
"id": "123456",
"name": "EXAMPLE LIMITED",
"countryCode": "GB",
"status": "Active",
"source": {
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
}
},
"query": {
"identifier": "EXAMPLE LIMITED",
"country": "GB",
"subdivision": ""
},
"ubo": {
"company": {
"id": "123456",
"name": "EXAMPLE LIMITED",
"country_code": "GB",
"status": "Active"
},
"summary": {
"as_of": "2026-11-08",
"threshold": 25,
"max_depth": 10,
"max_depth_reached": 1,
"total_ubos": 1,
"determination": "partial",
"chain_complete": false,
"ubo_determination_conclusive": false,
"percentages_are_minimum": true,
"risk_flags": [],
"data_warnings": [
"Ownership beyond Example Holdings is unavailable."
],
"ownership_breakdown": {
"ubo_percentage": 70,
"below_threshold_person_percentage": 0,
"listed_entity_percentage": 0,
"unresolved_entity_percentage": 30,
"untraced_percentage": 0,
"total_percentage": 100
},
"enrichment": {
"mode": "internal",
"status": "not_requested",
"units_consumed": 0
}
},
"data": [
{
"id": "example-person",
"full_name": "Alex Example",
"first_name": "Alex",
"last_name": "Example",
"nationality": null,
"date_of_birth": null,
"address": null,
"status": null,
"ceased_on": null,
"notified_on": null,
"country_of_residence": null,
"is_pep": null,
"control_types": [
"ownership"
],
"ownership": {
"direct_percentage": 70,
"indirect_percentage": 0,
"effective_percentage": 70,
"voting_percentage": null,
"has_direct_ownership": true,
"has_indirect_ownership": false
},
"paths_total": 1,
"paths_truncated": false,
"paths": [
{
"type": "direct",
"depth": 1,
"path_percentage": 70,
"nodes": [
{
"name": "EXAMPLE LIMITED",
"type": "company",
"level": 0,
"entity_id": "123456",
"country_code": "GB"
},
{
"name": "Alex Example",
"type": "person",
"level": 1,
"entity_id": "example-person",
"percentage": 70,
"percentage_derived": false
}
]
}
],
"provenance": {
"source": null,
"source_type": null,
"determination_type": "calculated",
"retrieved_at": "2026-11-08T08:00:00Z"
}
}
],
"terminal_entities": [
{
"entity_id": "654321",
"name": "EXAMPLE HOLDINGS",
"type": "company",
"country_code": "GB",
"effective_percentage": 30,
"reason": "Ownership information unavailable",
"path": [
{
"name": "EXAMPLE LIMITED",
"type": "company",
"level": 0,
"entity_id": "123456",
"country_code": "GB"
},
{
"name": "EXAMPLE HOLDINGS",
"type": "company",
"level": 1,
"entity_id": "654321",
"country_code": "GB",
"percentage": 30,
"percentage_derived": false
}
]
}
],
"total_results": 1,
"total_pages": 1
},
"summary": {
"directShareholderCount": 2,
"currentPercentageTotal": 100,
"shareholderSetComplete": true,
"ownershipChainComplete": false,
"ownershipPathCount": 1,
"corporateBranchCount": 1
},
"shareholders": [
{
"id": "example-person",
"name": "Alex Example",
"type": "person",
"percentage": 70,
"status": "current",
"source": {
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
},
"materialSources": [
{
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
}
],
"identitySources": [],
"eligibleForOwnershipCalculation": true,
"identityEligibleForAutomation": false,
"issueCodes": [
"source_unavailable"
]
},
{
"id": "654321",
"name": "EXAMPLE HOLDINGS",
"type": "company",
"percentage": 30,
"status": "current",
"source": {
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
},
"materialSources": [
{
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
}
],
"identitySources": [],
"linkedCompanyId": "654321",
"eligibleForOwnershipCalculation": true,
"identityEligibleForAutomation": false,
"issueCodes": [
"source_unavailable"
]
}
],
"officers": {
"status": "unavailable",
"checkedAt": "2026-11-08T08:00:00Z",
"candidates": [],
"excludedCount": 0,
"message": "Officer evidence was unavailable in this fictional example."
},
"ownershipPaths": [
{
"personName": "Alex Example",
"identityBasis": "provider_person_id",
"effectivePercentage": 70,
"provisional": true,
"path": [
{
"companyId": "123456",
"companyName": "EXAMPLE LIMITED",
"companyCountryCode": "GB",
"holderName": "Alex Example",
"holderType": "person",
"edgePercentage": 70,
"effectivePercentage": 70,
"source": {
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
},
"materialSources": [
{
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
}
],
"identitySources": []
}
]
}
],
"ultimateBeneficialOwners": [
{
"personName": "Alex Example",
"identityBasis": "provider_person_id",
"effectivePercentage": 70,
"exceedsThreshold": true,
"pathCount": 1,
"source": {
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
}
}
],
"corporateBranches": [
{
"holderName": "EXAMPLE HOLDINGS",
"directPercentage": 30,
"effectivePercentage": 30,
"ownershipSource": {
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
},
"materialSources": [
{
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
}
],
"identitySources": [],
"status": "unavailable",
"issueCodes": [
"ownership_unavailable"
],
"path": [
"EXAMPLE LIMITED",
"EXAMPLE HOLDINGS"
]
}
],
"group": {
"status": "not_found",
"usedForOwnershipCalculation": false,
"path": [],
"nodes": []
},
"calculation": {
"thresholdPercentage": 25,
"denominatorTolerance": 0.5,
"provenance": {
"name": "Illustrative source; no registry finding",
"category": "Example",
"provenanceClass": "unresolved",
"provenanceState": "unresolved",
"retrievedAt": "2026-11-08T08:00:00Z"
}
},
"retrievalTrace": [],
"warnings": [
"Fictional example. Source provenance and part of the ownership chain are unavailable."
],
"unresolvedGaps": [
"Unresolved corporate ownership: 30%",
"Source provenance is unavailable."
],
"determinationOutcome": "incomplete",
"billing": {
"units": 1,
"reason": "ownership_evidence",
"message": "Usable partial ownership evidence was returned."
},
"isSample": false,
"previewOnly": false,
"generatedAt": "2026-11-08T08:00:00Z",
"requestId": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee"
}
}Usage and limits
/usageReturns workspace, investigations, searches and limits. The investigation counter contains paid ownership reports only. Free accounts show zero available investigations and a separate companyPreviews counter. Reserved reports are already excluded from remaining; do not subtract them again. API search resets are returned as UNIX timestamps in UTC.
Usage examples: paid and free workspaces
These balances and allowances are illustrative. Read your workspace’s returned values; do not hard-code the example limits.
{
"workspace": {
"id": "example-workspace",
"name": "Example workspace",
"plan": "Pay as you go"
},
"investigations": {
"used": 1,
"reserved": 0,
"remaining": 1,
"limit": 2,
"windowStart": 1793491200,
"windowEnd": 1796083200
},
"searches": {
"used": 1,
"limit": 100,
"remaining": 99,
"resetsAt": 1794182400
},
"limits": {
"requestsPerSecond": 1,
"maxActiveKeys": 5
}
}{
"workspace": {
"id": "example-workspace",
"name": "Example workspace",
"plan": "Free"
},
"investigations": {
"used": 0,
"reserved": 0,
"remaining": 0,
"limit": 0,
"windowStart": 1794096000,
"windowEnd": 1794182400
},
"companyPreviews": {
"used": 0,
"remaining": 3,
"limit": 3,
"resetsAt": 1794182400
},
"searches": {
"used": 1,
"limit": 3,
"remaining": 2,
"resetsAt": 1794182400
},
"limits": {
"requestsPerSecond": 1,
"maxActiveKeys": 5
}
}Allow one data request per second across all your API keys and MCP calls. Discovery and tool-list requests do not consume that rate allowance. API search allowance is separate from report credits; API and MCP share it.
Starting a report can consume one investigation. Polling the same run, reading saved evidence and checking usage do not each create a new charge. The returned billing object records the result’s charge.
Errors and retries
REST errors contain error.code and error.message. Application errors include a requestId; gateway failures may omit it. Keep any request ID when contacting support. REST JSON bodies are limited to 16 KiB; MCP bodies to 32 KiB.
| Status | Meaning | Action |
|---|---|---|
| 402 | Report credit required | Buy a report or add credit in Billing. |
| 401 | Missing, expired or revoked key | Replace or correct the key. |
| 403 | Read-only permission or inactive access | Check key permissions and billing. |
| 404 | Case or run not found in this workspace | Check the saved ID and workspace. |
| 409 | Run in progress, conflicting settings or terminal failure | Inspect run_in_progress, run_input_changed or run_failed; follow the recovery rules above. |
| 410 | Upstream job expired | Start a new run with a new UUID. |
| 413 | Request body too large | Reduce the JSON body to the documented limit. |
| 415 / 422 | Wrong content type or invalid fields | Send application/json and correct the request. |
| 429 · rate_limited | Workspace request rate reached | Wait for Retry-After, then retry. |
| 429 · search_limit_reached / usage_limit_reached | Search or investigation allowance reached | Check /usage or change the plan; do not retry continuously. |
| 503 | Provider or service failure | Retry the same UUID first; if run_failed confirms a terminal run, start a new UUID. |
{
"status": "error",
"requestId": "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
"error": {
"code": "report_purchase_required",
"message": "Paid report credit is required."
}
}Use the API from a server or a client secret store. Browser CORS access is restricted to the Zavia dashboard; putting a secret in a public website is unsupported. GET event streams, JSON-RPC batches and background webhook delivery are not provided. Poll the investigation endpoint for completion.
Paths use version /api/v1. Additional response fields may be added; clients should ignore fields they do not use. Unknown request fields are rejected. The OpenAPI document describes field types, optional values and response codes.
MCP for AI agents
https://connect.zavia.ai/mcpUse Streamable HTTP with the same Bearer key. Protocol 2026-07-28 supports server/discover, tools/list and tools/call. Older clients can use the 2025-03-26, 2025-06-18 or 2025-11-25 initialize handshake.
Modern requests carry matching MCP-Protocol-Version, Mcp-Method and, for tool calls, Mcp-Name headers, plus protocol version, client information and capabilities in params._meta.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "zavia_get_usage",
"arguments": {},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "your-client",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}| MCP tool | REST equivalent |
|---|---|
zavia_search_companies | POST /search |
zavia_investigate_company | POST /investigations |
zavia_list_investigations | GET /cases |
zavia_get_investigation | GET /cases/{caseId} |
zavia_get_usage | GET /usage |
MCP returns the same data fields and uses the same run IDs, permissions and allowance as REST. Read-only keys do not expose the investigation tool. Successful tool calls return both text content and structured data. Tool failures set isError: true; inspect structuredContent.error. Authentication and protocol errors use HTTP and JSON-RPC error responses.