Get started
Overview
https://api.getzorro.ai/v1
There are two ways to call the API: GET /v1/resolve returns one company in the same request; POST /v1/enrich/jobs accepts a list as a job that you poll and page through. Both return the same company file for each company.
The OpenAPI description covers all 10 operations and 62 schemas: the
response format, rows, jobs, errors, every reason code and the value of each block.
The Postman collection is generated from it: set the ZORRO_API_KEY
collection variable and run.
If you would rather not write integration code, the MCP server exposes the same company intelligence as tools for Claude, ChatGPT, and any MCP-compatible client.
Quick start
Resolve one company
GET /v1/resolve takes one of company_number,
company_name or domain, and a comma-separated
blocks list. With defer_live=true, the initial response may mark a block as pending; retrieve that block from results_url:
curl "https://api.getzorro.ai/v1/resolve?company_number=02805730&blocks=domain,domain_validation,nature_of_business,property,subsidiaries&defer_live=true" \
-H "Authorization: Bearer $ZORRO_API_KEY" {
"ref": "",
"status": "pending",
"blocks": {
"domain": {
"status": "available",
"value": "hotelchocolat.com",
"source": "cache"
},
"domain_validation": {
"status": "pending"
},
"nature_of_business": {
"status": "not_found",
"reason": "no_description_on_file",
"detail": {
"website_verdict": {
"domain": "hotelchocolat.com",
"outcome": "accept",
"score": 100,
"confidence": 1,
"checked_at": "2026-09-15T07:07:48Z",
"auto_run": false,
"served": false
}
}
},
"property": {
"status": "available",
"value": {
"title_count": 6,
"tenure": [
"Freehold",
"Leasehold"
],
"postcodes": [
"PE29 7HA",
"PE19 8JJ",
"PE29 7DQ"
],
"addresses": [
"Land lying to the west of Sallowbush Road, Huntingdon",
"3 Redwongs Way, Huntingdon, (PE29 7HA)",
"1a and, 2b Alpha Drive, Eaton Socon, St Neots (PE19 8JJ)",
"Topper Cases Ltd, 1 Glebe Road, Huntingdon (PE29 7DQ)",
"Land lying to the west of Sallowbush Road, Huntingdon",
"Unit SU31, Block E/F, Southgate, Bath"
]
},
"source": "hm_land_registry"
},
"subsidiaries": {
"status": "available",
"value": {
"subsidiaries": [
{
"company_number": "10487072",
"company_name": "RABOT 1745 LIMITED",
"company_status": "Dissolved",
"natures_of_control": [
"ownership-of-shares-75-to-100-percent",
"voting-rights-75-to-100-percent"
],
"ownership_band": "75–100%",
"notified_on": "2017-10-30",
"matched_on": "registration_number"
},
{
"company_number": "02174370",
"company_name": "HOTEL CHOCOLAT CORPORATE LTD",
"company_status": "Dissolved",
"natures_of_control": [
"ownership-of-shares-75-to-100-percent",
"voting-rights-75-to-100-percent"
],
"ownership_band": "75–100%",
"notified_on": "2016-04-06",
"matched_on": "registration_number"
}
],
"count": 2,
"active_count": 0
},
"source": "companies_house_psc",
"as_of": "2017-10-30"
}
},
"pending_blocks": [
"domain_validation"
],
"matched": {
"company_number": "02805730",
"company_name": "HOTEL CHOCOLAT LIMITED",
"match_confidence": 1,
"matched_on": "company_number"
},
"job_id": "4d107210-fa63-49f6-be73-446f989a6f2e",
"expires_at": null,
"complete": false,
"results_url": "https://api.getzorro.ai/v1/enrich/jobs/4d107210-fa63-49f6-be73-446f989a6f2e/results",
"status_url": "https://api.getzorro.ai/v1/enrich/jobs/4d107210-fa63-49f6-be73-446f989a6f2e",
"retry_after_ms": 1000
} Poll results_url, waiting retry_after_ms between calls, until results_ready is true:
curl "https://api.getzorro.ai/v1/enrich/jobs/4d107210-fa63-49f6-be73-446f989a6f2e/results" \
-H "Authorization: Bearer $ZORRO_API_KEY" Response · 200, the finished row
{
"results": [
{
"ref": "",
"status": "done",
"blocks": {
"domain": {
"status": "available",
"value": "hotelchocolat.com",
"source": "cache",
"final_url": "https://www.hotelchocolat.com/uk"
},
"domain_validation": {
"status": "available",
"value": {
"domain": "hotelchocolat.com",
"dns_resolves": true,
"email_deliverable": true,
"site_live": true,
"http_status": 200,
"final_url": "https://www.hotelchocolat.com/uk",
"director_check": "companies_house",
"score": 100,
"outcome": "accept",
"checks": {
"legal_name_exact": 0.5,
"business_name": true,
"same_postcode": true,
"similar_postcode": true,
"city_match": true,
"county_match": true,
"address_match": true,
"director_match": false,
"company_number": true,
"other_company_number": false,
"foreign_registration": false,
"uk_domain": false,
"phone_present": true,
"generic_platform": false,
"parked": false
},
"contributions": {
"legal_name_exact": 15,
"business_name": 15,
"same_postcode": 25,
"similar_postcode": 8,
"city_match": 10,
"county_match": 5,
"address_match": 15,
"phone_present": 3,
"company_number": 50
},
"reasons": [
"all checks passed"
]
},
"source": "zorro_validator",
"confidence": 1,
"flagged_for_review": false,
"detail": {
"evidence": {
"hard_matches": {
"company_number": {
"url": "https://www.hotelchocolat.com/uk/i/terms-and-conditions.html",
"snippet": "…ngdom. Registered in England and Wales under company number 2805730. VAT number 945695766 (“Hotel Chocolat”) 2. Information Abo…"
},
"postcode": {
"url": "https://www.hotelchocolat.com/uk",
"snippet": "…pp © Hotel Chocolat 2026. Mint House, Newark Close, Royston SG8 5HL, UK An Affiliate of Mars, Incorporated Terms & Conditions S…"
},
"registered_address": {
"url": "https://www.hotelchocolat.com/uk",
"snippet": "…s Sign Up Follow Us Download Our App © Hotel Chocolat 2026. Mint House, Newark Close, Royston SG8 5HL, UK An Affiliate of Mars, In…"
}
},
"name_match": {
"kind": "trading_name",
"url": "https://www.hotelchocolat.com/uk",
"snippet": "Luxury Chocolates | Chocolate Gifts & Hampers | Hotel Chocolat Skip to Content Help Help & Support FREE Standard Delivery…"
}
}
}
},
"nature_of_business": {
"status": "not_found",
"reason": "no_description_on_file",
"detail": {
"website_verdict": {
"domain": "hotelchocolat.com",
"outcome": "accept",
"score": 100,
"confidence": 1,
"checked_at": "2026-09-15T07:07:48Z",
"auto_run": false,
"served": false
}
}
},
"property": {
"status": "available",
"value": {
"title_count": 6,
"tenure": [
"Freehold",
"Leasehold"
],
"postcodes": [
"PE29 7HA",
"PE19 8JJ",
"PE29 7DQ"
],
"addresses": [
"Land lying to the west of Sallowbush Road, Huntingdon",
"3 Redwongs Way, Huntingdon, (PE29 7HA)",
"1a and, 2b Alpha Drive, Eaton Socon, St Neots (PE19 8JJ)",
"Topper Cases Ltd, 1 Glebe Road, Huntingdon (PE29 7DQ)",
"Land lying to the west of Sallowbush Road, Huntingdon",
"Unit SU31, Block E/F, Southgate, Bath"
]
},
"source": "hm_land_registry"
},
"subsidiaries": {
"status": "available",
"value": {
"subsidiaries": [
{
"company_number": "10487072",
"company_name": "RABOT 1745 LIMITED",
"company_status": "Dissolved",
"natures_of_control": [
"ownership-of-shares-75-to-100-percent",
"voting-rights-75-to-100-percent"
],
"ownership_band": "75–100%",
"notified_on": "2017-10-30",
"matched_on": "registration_number"
},
{
"company_number": "02174370",
"company_name": "HOTEL CHOCOLAT CORPORATE LTD",
"company_status": "Dissolved",
"natures_of_control": [
"ownership-of-shares-75-to-100-percent",
"voting-rights-75-to-100-percent"
],
"ownership_band": "75–100%",
"notified_on": "2016-04-06",
"matched_on": "registration_number"
}
],
"count": 2,
"active_count": 0
},
"source": "companies_house_psc",
"as_of": "2017-10-30"
}
},
"matched": {
"company_number": "02805730",
"company_name": "HOTEL CHOCOLAT LIMITED",
"match_confidence": 1,
"matched_on": "company_number"
}
}
],
"next_cursor": null,
"remaining": 0,
"results_ready": true,
"expires_at": "2026-10-15T07:16:26.303575Z"
}
A request for an unknown company returns 404. A name that matches more than one live company returns 409 with the candidates, so you can retry with a company_number:
HTTP/1.1 404 Not Found
{
"detail": "not_matched"
} HTTP/1.1 409 Conflict
{
"detail": "ambiguous_match",
"candidates": [
{
"company_number": "00445790",
"company_name": "TESCO PLC"
},
{
"company_number": "00519500",
"company_name": "TESCO STORES LIMITED"
},
{
"company_number": "08730224",
"company_name": "TESCO SECRETARIES LIMITED"
},
{
"company_number": "00243011",
"company_name": "TESCO HOLDINGS LIMITED"
},
{
"company_number": "04345023",
"company_name": "TESCO FREETIME LIMITED"
}
]
} Submit a list as a job
POST /v1/enrich/jobs takes a list of rows and the blocks to return for each row. A job can carry up to your key's row limit
(limits.max_rows_per_job in GET /v1/whoami, at most
250,000); more rows are refused with 413. The response contains a job_id before the asynchronous job is complete.
curl -X POST "https://api.getzorro.ai/v1/enrich/jobs" \
-H "Authorization: Bearer $ZORRO_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: docs-capture-2026-09-15-websites-1" \
-d '{
"rows": [
{
"ref": "crm-1001",
"company_number": "02805730"
},
{
"ref": "crm-1002",
"company_number": "08130873"
},
{
"ref": "crm-1003",
"company_number": "07706156"
}
],
"blocks": [
"domain",
"domain_validation"
],
"options": {
"validation": [
"rules",
"live"
]
}
}' HTTP/1.1 202 Accepted
{
"job_id": "d99ba955-f280-4723-b49a-fbb3dd872875",
"status": "queued",
"blocks": [
"domain",
"domain_validation"
],
"counts": {
"total": 3,
"processed": 0,
"succeeded": 0,
"not_found": 0,
"failed": 0,
"site_unreachable": 0
},
"delivery": {
"mode": "pull"
},
"results_ready": false,
"expires_at": null,
"created_at": "2026-09-15T07:07:47.705727Z",
"finished_at": null
} Poll the job, waiting retry_after_ms, until results_ready is true, then page through results with the cursor. See Jobs and progressive answers.
Explore a company file.
Then paste your key and run your own.
Open three demo company files without a key. Add your key to search for UK companies and select the blocks available to it.
- Detected entity
- Industry (from site)
- USP
- Inferred goals
- Legal type
- Nature of business
- Registered office
- Officers · PSC
- Group
View raw API response
ZORRO_KEY, never from the key pasted here.Authentication
Every request carries a bearer key in the Authorization header:
Authorization: Bearer zk_live_abcdefghjkmn_<secret>
A key is made of a prefix (zk_live_ or zk_test_), a 12-character key id and the secret. You can log the key id and quote it to support; the secret is shown once, when the key is created, and cannot be shown again. Any failure (no key, a
malformed key, a revoked key, a wrong secret) answers 401 with the
same message, Invalid API key. (When there is no header, the message is “Authentication credentials were not provided.”).
/v1 accepts API keys only. A session token from the Zorro app or an
OAuth token from the MCP server is refused here.
This API is meant for server-to-server calls. A request to /v1 from a browser tab will fail with an opaque CORS error. A key in browser or mobile code is also a key anyone can read. Call it from your backend; test with curl or the tester above.
Keys and access
Scopes
| Scope | Grants |
|---|---|
read | This scope grants access to GET /v1/whoami, GET /v1/blocks, GET /v1/usage, and a job's status, results and errors. |
enrich | This scope grants access to GET /v1/resolve, submitting a job and cancelling a job; these calls are billed. |
A key without the scope an endpoint needs gets 403 with a message
naming the missing scope and the scopes the key has.
Live and test keys
zk_live_ and zk_test_ are labels on the key, and a key only
authenticates with its own prefix. A test key is not a sandbox: it
returns the same data and is recorded in usage exactly like a live key. Use it to keep
test traffic apart in your own logs and in GET /v1/usage.
See what your key can do
One call to GET /v1/whoami returns the scopes, every block with its
availability (available or not_enabled) and
tier (core or premium), the row cap and the
per-minute request allowance. GET /v1/blocks gives the same availability per
block, with each block's description and source. Neither call is billed, and neither changes data.
curl "https://api.getzorro.ai/v1/whoami" -H "Authorization: Bearer $ZORRO_API_KEY" Response · 200
{
"key_id": "abcdefghjkmn",
"name": "Example Bank production",
"environment": "live",
"scopes": [
"read",
"enrich"
],
"account": {
"id": 1234,
"email": "[email protected]"
},
"blocks": [
{
"key": "domain",
"availability": "available",
"tier": "core"
},
{
"key": "related_company_domain",
"availability": "available",
"tier": "premium"
},
{
"key": "domain_validation",
"availability": "available",
"tier": "core"
},
{
"key": "nature_of_business",
"availability": "available",
"tier": "premium"
},
{
"key": "registered_address",
"availability": "available",
"tier": "core"
},
{
"key": "corporate_owners",
"availability": "available",
"tier": "core"
},
{
"key": "controllers",
"availability": "available",
"tier": "core"
},
{
"key": "charges",
"availability": "available",
"tier": "core"
},
{
"key": "property",
"availability": "available",
"tier": "premium"
},
{
"key": "officers",
"availability": "available",
"tier": "core"
},
{
"key": "company_identity",
"availability": "available",
"tier": "core"
},
{
"key": "filing_status",
"availability": "available",
"tier": "core"
},
{
"key": "registry_events",
"availability": "available",
"tier": "core"
},
{
"key": "financials_filed",
"availability": "available",
"tier": "premium"
},
{
"key": "ultimate_owner",
"availability": "available",
"tier": "premium"
},
{
"key": "employees_filed",
"availability": "available",
"tier": "premium"
},
{
"key": "industry",
"availability": "available",
"tier": "core"
},
{
"key": "contacts",
"availability": "available",
"tier": "premium"
},
{
"key": "signals",
"availability": "available",
"tier": "premium"
},
{
"key": "news_signals",
"availability": "available",
"tier": "premium"
},
{
"key": "financials_live",
"availability": "available",
"tier": "premium"
},
{
"key": "employees_live",
"availability": "available",
"tier": "premium"
},
{
"key": "web_traffic",
"availability": "available",
"tier": "premium"
},
{
"key": "web_presence",
"availability": "available",
"tier": "premium"
},
{
"key": "technologies",
"availability": "available",
"tier": "premium"
},
{
"key": "employees",
"availability": "available",
"tier": "premium"
},
{
"key": "planning",
"availability": "available",
"tier": "premium"
},
{
"key": "financial_benchmarks",
"availability": "available",
"tier": "premium"
},
{
"key": "revenue_estimate",
"availability": "available",
"tier": "premium"
},
{
"key": "scores",
"availability": "available",
"tier": "premium"
},
{
"key": "funding",
"availability": "available",
"tier": "premium"
},
{
"key": "trading_address",
"availability": "available",
"tier": "premium"
},
{
"key": "subsidiaries",
"availability": "available",
"tier": "premium"
},
{
"key": "firmographics",
"availability": "available",
"tier": "premium"
},
{
"key": "comparables",
"availability": "available",
"tier": "premium"
},
{
"key": "business_intelligence",
"availability": "available",
"tier": "premium"
},
{
"key": "company_summary",
"availability": "not_enabled",
"tier": "premium"
}
],
"blocks_enabled_count": 36,
"blocks_total": 37,
"limits": {
"max_rows_per_job": 250000,
"max_rows_per_job_source": "default",
"rate_limits": {
"requests_per_minute": 600,
"resolve_per_minute": 60
},
"expires_at": null
},
"docs": {
"url": "https://getzorro.ai/docs/api",
"openapi": "/v1/openapi.json",
"catalog": "/v1/blocks"
}
} | Field | Meaning |
|---|---|
limits.max_rows_per_job | The most rows one job may carry for this key. max_rows_per_job_source says key when your key has its own lower cap, default when the 250,000 cap applies. A larger job answers 413. |
limits.rate_limits | This field gives the requests per minute for this key, across all endpoints and for GET /v1/resolve on its own. |
limits.expires_at | The value is always null. Keys do not expire. A key stops working only when it is revoked. |
Asking for a block your key cannot have
A request is not refused because one of several blocks is unavailable. The blocks the key may use are resolved; the others come back {"status": "not_enabled", "reason": "no_entitlement"}
and one warning names them beside the data:
{
"warnings": [
{
"code": "blocks_not_enabled",
"blocks": [
"company_summary"
],
"detail": "Not enabled for your key: company_summary. Returned as not_enabled; every other block is unaffected."
}
],
"blocks": {
"company_summary": {
"status": "not_enabled",
"reason": "no_entitlement"
}
}
}
The request is refused only when the key cannot have any of the blocks asked for.
It then answers 403 with the same per-block shape:
{
"detail": "Not enabled for this account: company_summary.",
"blocks": {
"company_summary": {
"status": "not_enabled",
"reason": "no_entitlement"
}
}
}
Blocks marked experimental in the catalogue are off by default and switched on per account. The same applies to the web_search option: if it is not enabled, a resolve or a job that asks for it is refused with 403 rather than run without it.
Rotation and revocation
Zorro creates and revokes keys when you ask. An account can hold two
live keys at once, so rotation overlaps: we create the second key, you move your
traffic to it, we revoke the first. A revoked key answers 401. The
per-minute allowance is counted per key; idempotency keys and jobs belong to the
account, so either key can read a job the other created.
Common tasks
Call prep by company number
Use this before a call to get a company's register details in one request, by company number.
With defer_live=true, the initial response may show a block as pending; retrieve that block from results_url.
curl "https://api.getzorro.ai/v1/resolve?company_number=02805730&blocks=company_identity,registered_address,filing_status,officers,controllers,corporate_owners,ultimate_owner,charges,industry&defer_live=true" \
-H "Authorization: Bearer $ZORRO_API_KEY" Read these fields first:
- Use
company_identity.value.company_statusandstatus_groupto determine whether the company is trading. filing_statuscontains the filing dates and any overdue flags.officers.value.currentidentifies who runs the company.as_ofgives the date of the list.controllersandcorporate_ownersidentify who controls the company. An empty list means no controller is recorded.charges.value.holdersidentifies who holds charges over the company, as named on the register.
The row for Hotel Chocolat Limited (02805730)
This example is shortened: officers.value.former shows 2 of 17 entries
and charges.value.satisfied_charges shows 1 of 17.
{
"ref": "",
"status": "done",
"blocks": {
"company_identity": {
"status": "available",
"value": {
"incorporated_on": "1993-04-01",
"company_category": "Private Limited Company",
"country_of_origin": "United Kingdom",
"legal_form": "Limited",
"company_status": "Active",
"status_group": "active",
"trading_name": "Hotel Chocolat",
"trading_name_source": "third_party_profile"
},
"source": "companies_house"
},
"registered_address": {
"status": "available",
"value": {
"address_line_1": "MINT HOUSE",
"address_line_2": "NEWARK CLOSE",
"locality": "ROYSTON",
"region": "HERTFORDSHIRE",
"postal_code": "SG8 5HL"
},
"source": "companies_house"
},
"filing_status": {
"status": "available",
"value": {
"accounts_last_made_up_on": "2024-12-28",
"accounts_next_due_on": "2026-09-28",
"accounts_category": "AUDIT EXEMPTION SUBSIDIARY",
"confirmation_statement_last_made_up_on": "2026-03-31",
"confirmation_statement_next_due_on": "2027-04-14"
},
"source": "companies_house",
"detail": {
"freshness": {
"checked_live": false
}
},
"as_of": "2026-07-01"
},
"officers": {
"status": "available",
"value": {
"total": 21,
"active": 4,
"current": [
{
"name": "NICOLA LACEY",
"role": "Director",
"appointed_on": "2024-08-23",
"nationality": "BRITISH",
"country_of_residence": "UNITED KINGDOM",
"occupation": "DIRECTOR",
"person_number": "184966970001",
"person_key": "18496697",
"date_of_birth": {
"month": 10,
"year": 1972
}
},
{
"name": "DANIEL MARK JENKS",
"role": "Director",
"appointed_on": "2026-02-28",
"nationality": "BRITISH",
"country_of_residence": "UNITED KINGDOM",
"occupation": null,
"person_number": "346063570001",
"person_key": "34606357",
"date_of_birth": {
"month": 7,
"year": 1983
}
},
{
"name": "Isabel Louise STATON",
"role": "Director",
"appointed_on": "2026-04-20",
"nationality": "British",
"country_of_residence": "United Kingdom",
"occupation": null,
"person_number": "XlZ47f6Fh_bvCYU-tcCitVk7d6w",
"date_of_birth": {
"month": 12,
"year": 1978
}
},
{
"name": "INDIGO CORPORATE SECRETARY LIMITED",
"role": "Secretary",
"appointed_on": "2021-06-30",
"nationality": null,
"country_of_residence": null,
"occupation": null,
"person_number": "281695990002",
"person_key": "28169599",
"is_corporate": true
}
],
"resigned": 17,
"former": [
{
"name": "GREGOIRE ALEXANDRE GAILLOT",
"role": "Director",
"appointed_on": "2024-01-25",
"nationality": "FRENCH",
"country_of_residence": "UNITED STATES",
"occupation": "FINANCE DIRECTOR",
"person_number": "318583360001",
"person_key": "31858336",
"date_of_birth": {
"month": 12,
"year": 1980
},
"resigned_on": "2026-04-20"
},
{
"name": "LYSA MARIA HARDY",
"role": "Director",
"appointed_on": "2024-08-23",
"nationality": "BRITISH",
"country_of_residence": "UNITED KINGDOM",
"occupation": "DIRECTOR",
"person_number": "165408450002",
"person_key": "16540845",
"date_of_birth": {
"month": 4,
"year": 1970
},
"resigned_on": "2025-08-27"
}
],
"ages": {
"average": 57.9,
"youngest": 43,
"oldest": 80
}
},
"source": "companies_house",
"as_of": "2026-06-01",
"detail": {
"freshness": {
"checked_live": false
}
}
},
"controllers": {
"status": "available",
"value": {
"controllers": [
{
"type": "company",
"name": "Hotel Chocolat Group Ltd",
"country_registered": "England And Wales",
"natures_of_control": [
"ownership-of-shares-75-to-100-percent",
"voting-rights-75-to-100-percent"
],
"notified_on": "2016-04-06",
"company_number": "08612206"
}
],
"has_corporate_owner": true,
"control": {
"has_corporate_controller": true,
"has_individual_controller": false,
"no_controller_on_register": false,
"statement": null
},
"ceased_controllers": []
},
"source": "companies_house_psc",
"detail": {
"freshness": {
"checked_live": false
}
},
"as_of": "2026-07-01"
},
"corporate_owners": {
"status": "available",
"value": {
"has_corporate_owner": true,
"owners": [
{
"name": "Hotel Chocolat Group Ltd",
"natures_of_control": [
"ownership-of-shares-75-to-100-percent",
"voting-rights-75-to-100-percent"
],
"notified_on": "2016-04-06",
"country_registered": "England And Wales",
"company_number": "08612206",
"nature_of_business": {
"status": "not_found",
"reason": "no_description_on_file"
}
}
]
},
"source": "companies_house_psc"
},
"ultimate_owner": {
"status": "available",
"value": {
"company_name": "HOTEL CHOCOLAT GROUP LIMITED",
"company_number": "08612206",
"depth": 1,
"resolved_by": "self",
"chain": [
{
"company_name": "HOTEL CHOCOLAT GROUP LIMITED",
"company_number": "08612206"
}
]
},
"source": "companies_house_psc"
},
"charges": {
"status": "available",
"value": {
"outstanding_count": 0,
"satisfied_count": 17,
"total_count": 17,
"holders": [],
"charges": [],
"satisfied_charges": [
{
"charge_code": "028057300017",
"status": "satisfied",
"created_on": "2021-07-16",
"registered_on": "2021-07-21",
"persons_entitled": [
"LLOYDS BANK PLC AS SECURITY TRUSTEE FOR THE SECURED PARTIES"
],
"particulars": "N/A CONTAINS FIXED CHARGE. CONTAINS FLOATING CHARGE. FLOATING CHARGE COVERS ALL THE PROPERTY OR UNDERTAKING OF THE COMPANY. CONTAINS NEGATIVE PLEDGE.",
"contains": [
"fixed_charge",
"floating_charge",
"floating_charge_covers_all",
"negative_pledge"
],
"satisfied_on": "2024-02-23"
}
],
"past_holders": [
"LLOYDS BANK PLC AS SECURITY TRUSTEE FOR THE SECURED PARTIES",
"LLOYDS BANK CORPORATE MARKETS PLC",
"LLOYDS BANK PLC",
"LLOYDS TSB BANK PLC",
"TSB BANK PLC",
"LOMBARD NORTH CENTRAL PLC",
"BARCLAYS BANK PLC"
],
"latest_created_on": "2021-07-16",
"latest_satisfied_on": "2024-02-23"
},
"source": "companies_house",
"as_of": "2021-07-21",
"detail": {
"freshness": {
"checked_live": false
}
}
},
"industry": {
"status": "available",
"value": {
"sic": [
{
"code": "10821",
"description": "Manufacture of cocoa and chocolate confectionery",
"scheme": "sic_2007",
"is_primary": true
}
],
"label": "food and beverage manufacturing",
"label_source": "sic",
"nace": [
{
"code": "10.82",
"from_sic": "10821"
}
],
"sic_is_generic": false
},
"source": "companies_house",
"derived": true
}
},
"matched": {
"company_number": "02805730",
"company_name": "HOTEL CHOCOLAT LIMITED",
"match_confidence": 1,
"matched_on": "company_number"
}
} Websites for a list
To find websites for a list of companies, submit a job with domain and domain_validation and page through the results. Use a website only when domain_validation.value.outcome is accept and the block is not flagged. Treat review, reject, not_found and answers for sites that could not be read as meaning there is no website.
curl -X POST "https://api.getzorro.ai/v1/enrich/jobs" \
-H "Authorization: Bearer $ZORRO_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: docs-capture-2026-09-15-websites-1" \
-d '{
"rows": [
{
"ref": "crm-1001",
"company_number": "02805730"
},
{
"ref": "crm-1002",
"company_number": "08130873"
},
{
"ref": "crm-1003",
"company_number": "07706156"
}
],
"blocks": [
"domain",
"domain_validation"
],
"options": {
"validation": [
"rules",
"live"
]
}
}' A loop in Python
import time, requests
API = "https://api.getzorro.ai/v1"
HEADERS = {"Authorization": f"Bearer {ZORRO_API_KEY}"}
job = requests.post(f"{API}/enrich/jobs", headers={**HEADERS, "X-Idempotency-Key": "websites-2026-09-15-part-1"},
json={"rows": rows, "blocks": ["domain", "domain_validation"],
"options": {"validation": ["rules", "live"]}}).json()
while True:
status = requests.get(f"{API}/enrich/jobs/{job['job_id']}", headers=HEADERS).json()
if status["results_ready"]:
break
time.sleep(status.get("retry_after_ms", 5000) / 1000)
cursor = ""
while True:
page = requests.get(f"{API}/enrich/jobs/{job['job_id']}/results",
headers=HEADERS, params={"limit": 1000, "cursor": cursor}).json()
for row in page["results"]:
check = row["blocks"].get("domain_validation", {})
value = check.get("value") or {}
if check.get("status") == "available" and value.get("outcome") == "accept" and not check.get("flagged_for_review"):
accepted = value["domain"] # the website to use
else:
accepted = None # review, reject, unreadable or none on file: do not use
yield_row(row["ref"], accepted, check.get("reason"))
if not page["next_cursor"]:
break
cursor = page["next_cursor"] - Split large files into jobs you can re-run, each with its own
X-Idempotency-Key, so a retried submit never runs twice. - Jobs look for a website when the company has none on file (
options.discoverdefaults to["domain"]). Pass"discover": []to skip that search, or cap it withmax_live_lookups; rows past the cap answersource_busy. counts.site_unreachabletells you how many sites would not open; you can submit those rows again.- Keep
min_confidenceat the default 0.5 unless you have chosen another threshold for your own data.
The rows for this job (results page 1 of 2)
{
"results": [
{
"ref": "crm-1001",
"status": "done",
"blocks": {
"domain": {
"status": "available",
"value": "hotelchocolat.com",
"source": "cache",
"final_url": "https://www.hotelchocolat.com/uk"
},
"domain_validation": {
"status": "available",
"value": {
"domain": "hotelchocolat.com",
"dns_resolves": true,
"email_deliverable": true,
"site_live": true,
"http_status": 200,
"final_url": "https://www.hotelchocolat.com/uk",
"director_check": "companies_house",
"score": 100,
"outcome": "accept",
"checks": {
"legal_name_exact": 0.5,
"business_name": true,
"same_postcode": true,
"similar_postcode": true,
"city_match": true,
"county_match": true,
"address_match": true,
"director_match": false,
"company_number": true,
"other_company_number": false,
"foreign_registration": false,
"uk_domain": false,
"phone_present": true,
"generic_platform": false,
"parked": false
},
"contributions": {
"legal_name_exact": 15,
"business_name": 15,
"same_postcode": 25,
"similar_postcode": 8,
"city_match": 10,
"county_match": 5,
"address_match": 15,
"phone_present": 3,
"company_number": 50
},
"reasons": [
"all checks passed"
]
},
"source": "zorro_validator",
"confidence": 1,
"flagged_for_review": false,
"detail": {
"evidence": {
"hard_matches": {
"company_number": {
"url": "https://www.hotelchocolat.com/uk/i/terms-and-conditions.html",
"snippet": "…ngdom. Registered in England and Wales under company number 2805730. VAT number 945695766 (“Hotel Chocolat”) 2. Information Abo…"
},
"postcode": {
"url": "https://www.hotelchocolat.com/uk",
"snippet": "…pp © Hotel Chocolat 2026. Mint House, Newark Close, Royston SG8 5HL, UK An Affiliate of Mars, Incorporated Terms & Conditions S…"
},
"registered_address": {
"url": "https://www.hotelchocolat.com/uk",
"snippet": "…s Sign Up Follow Us Download Our App © Hotel Chocolat 2026. Mint House, Newark Close, Royston SG8 5HL, UK An Affiliate of Mars, In…"
}
},
"name_match": {
"kind": "trading_name",
"url": "https://www.hotelchocolat.com/uk",
"snippet": "Luxury Chocolates | Chocolate Gifts & Hampers | Hotel Chocolat Skip to Content Help Help & Support FREE Standard Delivery…"
}
}
}
}
},
"matched": {
"company_number": "02805730",
"company_name": "HOTEL CHOCOLAT LIMITED",
"match_confidence": 1,
"matched_on": "company_number"
}
},
{
"ref": "crm-1002",
"status": "done",
"blocks": {
"domain": {
"status": "available",
"value": "gymshark.com",
"source": "cache",
"final_url": "https://www.gymshark.com/"
},
"domain_validation": {
"status": "available",
"value": {
"domain": "gymshark.com",
"dns_resolves": true,
"email_deliverable": true,
"site_live": true,
"http_status": 200,
"final_url": "https://www.gymshark.com/",
"director_check": "companies_house",
"score": 95,
"outcome": "accept",
"checks": {
"legal_name_exact": true,
"business_name": true,
"same_postcode": false,
"similar_postcode": false,
"city_match": true,
"county_match": false,
"address_match": true,
"director_match": true,
"company_number": false,
"other_company_number": false,
"foreign_registration": false,
"uk_domain": false,
"phone_present": false,
"generic_platform": false,
"parked": false
},
"contributions": {
"legal_name_exact": 30,
"business_name": 15,
"city_match": 10,
"address_match": 15,
"director_match": 25
},
"reasons": [
"all checks passed"
]
},
"source": "zorro_validator",
"confidence": 0.95,
"flagged_for_review": false,
"detail": {
"evidence": {
"hard_matches": {
"director": {
"url": "https://www.gymshark.com/pages/about-us",
"snippet": "…e do today to prepare for tomorrow. Gymshark is led by: Ben Francis - Founder & Chief Executive Officer Noel Mack - Chief Brand…",
"surname": "francis"
}
},
"name_match": {
"kind": "legal_name",
"url": "https://www.gymshark.com/pages/terms-and-conditions",
"snippet": "…Language Selector dropdown English English Español © 2026 | Gymshark Limited | All Rights Reserved. | We Do Gym."
}
}
}
}
},
"matched": {
"company_number": "08130873",
"company_name": "GYMSHARK LTD",
"match_confidence": 1,
"matched_on": "company_number"
}
}
],
"next_cursor": "eyJzZXEiOjJ9",
"remaining": 1,
"results_ready": true,
"expires_at": "2026-10-15T07:09:32.659195Z"
} Reference
Endpoints
This reference is generated from the OpenAPI description (YAML, JSON; Postman collection). Every response is JSON, errors included.
- GET
/v1/openapi.json: Fetch this description - GET
/v1/whoami: Check a key and see its access - GET
/v1/blocks: List the block catalogue - GET
/v1/resolve: Resolve one company - POST
/v1/enrich/jobs: Submit a list as a job - GET
/v1/enrich/jobs/{job_id}: Get a job's status - GET
/v1/enrich/jobs/{job_id}/results: Page through a job's rows - GET
/v1/enrich/jobs/{job_id}/errors: Page through a job's quarantined rows - POST
/v1/enrich/jobs/{job_id}/cancel: Cancel a job - GET
/v1/usage: Usage by block or by day
GET /v1/openapi.json
This description, from the host it describes. The response is a 302 to where the file is published, which is the same document you are reading now; follow redirects, as every HTTP client and code generator does by default.
No key is needed, and the answer is the same for every account. /v1/openapi answers identically.
| Status | When | Body |
|---|---|---|
302 | Redirect to the published description. | |
429 | The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After. | Error |
500 | The request failed on Zorro's side. The body carries request_id when available; the X-Request-ID header always does. | Error |
curl "https://api.getzorro.ai/v1/openapi.json" \
-H "Authorization: Bearer $ZORRO_API_KEY" GET /v1/whoami
This call confirms the key works and returns what it can do: its scopes, every block with its availability and tier, the row cap and the per-minute request allowance. The call is not billed and does not change data.
| Status | When | Body |
|---|---|---|
200 | The key is valid. | WhoAmI |
401 | The key is missing, malformed, revoked or wrong. The message is the same in every case. | Error |
403 | The key lacks the scope this endpoint needs; the message names it. | Error |
429 | The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After. | Error |
500 | The request failed on Zorro's side. The body carries request_id when available; the X-Request-ID header always does. | Error |
curl "https://api.getzorro.ai/v1/whoami" \
-H "Authorization: Bearer $ZORRO_API_KEY" Example response · 200 · whoami.200
{
"key_id": "abcdefghjkmn",
"name": "Example Bank production",
"environment": "live",
"scopes": [
"read",
"enrich"
],
"account": {
"id": 1234,
"email": "[email protected]"
},
"blocks": [
{
"key": "domain",
"availability": "available",
"tier": "core"
},
{
"key": "related_company_domain",
"availability": "available",
"tier": "premium"
},
{
"key": "domain_validation",
"availability": "available",
"tier": "core"
},
{
"key": "nature_of_business",
"availability": "available",
"tier": "premium"
},
{
"key": "registered_address",
"availability": "available",
"tier": "core"
},
{
"key": "corporate_owners",
"availability": "available",
"tier": "core"
},
{
"key": "controllers",
"availability": "available",
"tier": "core"
},
{
"key": "charges",
"availability": "available",
"tier": "core"
},
{
"key": "property",
"availability": "available",
"tier": "premium"
},
{
"key": "officers",
"availability": "available",
"tier": "core"
},
{
"key": "company_identity",
"availability": "available",
"tier": "core"
},
{
"key": "filing_status",
"availability": "available",
"tier": "core"
},
{
"key": "registry_events",
"availability": "available",
"tier": "core"
},
{
"key": "financials_filed",
"availability": "available",
"tier": "premium"
},
{
"key": "ultimate_owner",
"availability": "available",
"tier": "premium"
},
{
"key": "employees_filed",
"availability": "available",
"tier": "premium"
},
{
"key": "industry",
"availability": "available",
"tier": "core"
},
{
"key": "contacts",
"availability": "available",
"tier": "premium"
},
{
"key": "signals",
"availability": "available",
"tier": "premium"
},
{
"key": "news_signals",
"availability": "available",
"tier": "premium"
},
{
"key": "financials_live",
"availability": "available",
"tier": "premium"
},
{
"key": "employees_live",
"availability": "available",
"tier": "premium"
},
{
"key": "web_traffic",
"availability": "available",
"tier": "premium"
},
{
"key": "web_presence",
"availability": "available",
"tier": "premium"
},
{
"key": "technologies",
"availability": "available",
"tier": "premium"
},
{
"key": "employees",
"availability": "available",
"tier": "premium"
},
{
"key": "planning",
"availability": "available",
"tier": "premium"
},
{
"key": "financial_benchmarks",
"availability": "available",
"tier": "premium"
},
{
"key": "revenue_estimate",
"availability": "available",
"tier": "premium"
},
{
"key": "scores",
"availability": "available",
"tier": "premium"
},
{
"key": "funding",
"availability": "available",
"tier": "premium"
},
{
"key": "trading_address",
"availability": "available",
"tier": "premium"
},
{
"key": "subsidiaries",
"availability": "available",
"tier": "premium"
},
{
"key": "firmographics",
"availability": "available",
"tier": "premium"
},
{
"key": "comparables",
"availability": "available",
"tier": "premium"
},
{
"key": "business_intelligence",
"availability": "available",
"tier": "premium"
},
{
"key": "company_summary",
"availability": "not_enabled",
"tier": "premium"
}
],
"blocks_enabled_count": 36,
"blocks_total": 37,
"limits": {
"max_rows_per_job": 250000,
"max_rows_per_job_source": "default",
"rate_limits": {
"requests_per_minute": 600,
"resolve_per_minute": 60
},
"expires_at": null
},
"docs": {
"url": "https://getzorro.ai/docs/api",
"openapi": "/v1/openapi.json",
"catalog": "/v1/blocks"
}
} GET /v1/blocks
This call returns every block with its description, source, availability for this key, tier, latency class and coverage, plus the confidence contract and the option vocabularies. The call is not billed and does not change data.
| Status | When | Body |
|---|---|---|
200 | The catalogue is returned. | BlockCatalog |
401 | The key is missing, malformed, revoked or wrong. The message is the same in every case. | Error |
403 | The key lacks the scope this endpoint needs; the message names it. | Error |
429 | The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After. | Error |
500 | The request failed on Zorro's side. The body carries request_id when available; the X-Request-ID header always does. | Error |
curl "https://api.getzorro.ai/v1/blocks" \
-H "Authorization: Bearer $ZORRO_API_KEY" GET /v1/resolve
Send one company and get its blocks back. The response carries a job_id, so the row can also be read from the job endpoints. The limit is 60 requests a minute per key.
With defer_live=true the response comes at once; blocks still being checked are {"status":"pending"} and are collected from results_url.
| Parameter | In | Type | Description |
|---|---|---|---|
company_number | query | string | This is the Companies House number. Give exactly one of company_number, company_name or domain. |
company_name | query | string | This is the registered name. More than one live match answers 409 with candidates. |
domain | query | string | Provide a bare host or URL. A register, directory, social or site-builder host answers 404 not_own_site. |
blocks | query | string | Provide comma-separated block keys. By default, every block enabled for the key is returned. Blocks the key cannot have come back not_enabled with a warning; if none is left, the request answers 403. |
validation | query | string | The domain_validation modes are none, rules, live, officers, ai. The default modes are rules,live. |
min_confidence | query | number | Set your threshold from 0 to 1. The default is 0.5. |
defer_live | query | "true" | "false" | When true, the response contains the available blocks; live blocks are pending and collected from results_url. This cannot be combined with wait=false or store=false. |
wait | query | "true" | "false" | When false, the response is 202 with the job and results_url; collect the row from there. |
store | query | "true" | "false" | When false, the row is not kept for later reads (expires_at null, stored false). |
discover | query | string | Specify the blocks to look up when the company has no value on file. This is off by default on resolve. |
max_live_lookups | query | integer | This sets the number of website searches allowed and defaults to 1 when discover is given. |
ref | query | string | This is your reference, which is echoed back. |
read_accounts_pdf | query | "true" | "false" | For financials_live, this reads PDF-only accounts. It is off by default. |
web_search | query | "true" | "false" | This is a Premium option enabled per account. |
google_business | query | "true" | "false" | For trading_address, this adds the listing check. |
contact_roles | query | string | For contacts, the values are owner, director, finance, operations, in order of preference. |
max_contacts | query | integer | For contacts, use 1 to 5; the default is 3. |
verify_emails | query | "true" | "false" | For contacts, the default is true. |
contacts_lookup | query | "cached_only" | "live" | For contacts, use cached_only or live; the default is live. |
include_phone | query | "true" | "false" | For contacts, this returns a phone number per person. With contacts_lookup=live, it needs defer_live=true or wait=false; otherwise, the response is 400. |
include_linkedin | query | "true" | "false" | For contacts, the default is true. |
X-Idempotency-Key | header | string | This makes a submit safe to retry. The same key with the same request returns the original job (200); with a different request, 409. It is scoped to the account and kept with the job. |
| Status | When | Body |
|---|---|---|
200 | The company is returned. With defer_live, the response may be incomplete. | ResolveResponse |
202 | With wait=false, the job is returned for you to poll. | Job |
400 | The input is invalid because it contains two identifiers, a conflicting flag pair, an unknown block or an invalid option (FieldErrors), or no usable identifier. | Error | FieldErrors |
401 | The key is missing, malformed, revoked or wrong. The message is the same in every case. | Error |
403 | The key lacks the enrich scope, or none of the requested blocks is enabled for it. | NotEnabledError | Error |
404 | No company matched. | ResolveNotFound |
409 | More than one live company matched, with candidates; or the X-Idempotency-Key was used for a different request. | AmbiguousMatch | Error |
429 | The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After. | Error |
500 | The request failed on Zorro's side. The body carries request_id when available; the X-Request-ID header always does. | Error |
502 | We could not resolve the row (detail gives a reason such as resolver_error or source_unavailable). Retry the request. | Error |
curl "https://api.getzorro.ai/v1/resolve?company_number=02805730&blocks=domain,domain_validation,nature_of_business,property,subsidiaries&defer_live=true" \
-H "Authorization: Bearer $ZORRO_API_KEY" Example response · 200 · resolve.progressive.first.200
With defer_live=true, domain, property, subsidiaries and nature_of_business are answered at once; domain_validation opens the site and is pending.
{
"ref": "",
"status": "pending",
"blocks": {
"domain": {
"status": "available",
"value": "hotelchocolat.com",
"source": "cache"
},
"domain_validation": {
"status": "pending"
},
"nature_of_business": {
"status": "not_found",
"reason": "no_description_on_file",
"detail": {
"website_verdict": {
"domain": "hotelchocolat.com",
"outcome": "accept",
"score": 100,
"confidence": 1,
"checked_at": "2026-09-15T07:07:48Z",
"auto_run": false,
"served": false
}
}
},
"property": {
"status": "available",
"value": {
"title_count": 6,
"tenure": [
"Freehold",
"Leasehold"
],
"postcodes": [
"PE29 7HA",
"PE19 8JJ",
"PE29 7DQ"
],
"addresses": [
"Land lying to the west of Sallowbush Road, Huntingdon",
"3 Redwongs Way, Huntingdon, (PE29 7HA)",
"1a and, 2b Alpha Drive, Eaton Socon, St Neots (PE19 8JJ)",
"Topper Cases Ltd, 1 Glebe Road, Huntingdon (PE29 7DQ)",
"Land lying to the west of Sallowbush Road, Huntingdon",
"Unit SU31, Block E/F, Southgate, Bath"
]
},
"source": "hm_land_registry"
},
"subsidiaries": {
"status": "available",
"value": {
"subsidiaries": [
{
"company_number": "10487072",
"company_name": "RABOT 1745 LIMITED",
"company_status": "Dissolved",
"natures_of_control": [
"ownership-of-shares-75-to-100-percent",
"voting-rights-75-to-100-percent"
],
"ownership_band": "75–100%",
"notified_on": "2017-10-30",
"matched_on": "registration_number"
},
{
"company_number": "02174370",
"company_name": "HOTEL CHOCOLAT CORPORATE LTD",
"company_status": "Dissolved",
"natures_of_control": [
"ownership-of-shares-75-to-100-percent",
"voting-rights-75-to-100-percent"
],
"ownership_band": "75–100%",
"notified_on": "2016-04-06",
"matched_on": "registration_number"
}
],
"count": 2,
"active_count": 0
},
"source": "companies_house_psc",
"as_of": "2017-10-30"
}
},
"pending_blocks": [
"domain_validation"
],
"matched": {
"company_number": "02805730",
"company_name": "HOTEL CHOCOLAT LIMITED",
"match_confidence": 1,
"matched_on": "company_number"
},
"job_id": "4d107210-fa63-49f6-be73-446f989a6f2e",
"expires_at": null,
"complete": false,
"results_url": "https://api.getzorro.ai/v1/enrich/jobs/4d107210-fa63-49f6-be73-446f989a6f2e/results",
"status_url": "https://api.getzorro.ai/v1/enrich/jobs/4d107210-fa63-49f6-be73-446f989a6f2e",
"retry_after_ms": 1000
} Example response · 200 · resolve.warning.200
company_summary is not enabled for the key: the other block resolves, and one warning names the refused block.
{
"ref": "",
"status": "done",
"blocks": {
"company_summary": {
"status": "not_enabled",
"reason": "no_entitlement"
}
},
"matched": {
"company_number": "02805730",
"company_name": "HOTEL CHOCOLAT LIMITED",
"match_confidence": 1,
"matched_on": "company_number"
},
"job_id": "a223350b-c286-4562-a524-1239a575ebef",
"expires_at": "2026-10-15T07:07:30.594324Z",
"warnings": [
{
"code": "blocks_not_enabled",
"blocks": [
"company_summary"
],
"detail": "Not enabled for your key: company_summary. Returned as not_enabled; every other block is unaffected."
}
],
"complete": true,
"pending_blocks": [],
"results_url": "https://api.getzorro.ai/v1/enrich/jobs/a223350b-c286-4562-a524-1239a575ebef/results",
"status_url": "https://api.getzorro.ai/v1/enrich/jobs/a223350b-c286-4562-a524-1239a575ebef"
} Example response · 404 · resolve.404.not_matched
{
"detail": "not_matched"
} Example response · 409 · resolve.409.ambiguous
{
"detail": "ambiguous_match",
"candidates": [
{
"company_number": "00445790",
"company_name": "TESCO PLC"
},
{
"company_number": "00519500",
"company_name": "TESCO STORES LIMITED"
},
{
"company_number": "08730224",
"company_name": "TESCO SECRETARIES LIMITED"
},
{
"company_number": "00243011",
"company_name": "TESCO HOLDINGS LIMITED"
},
{
"company_number": "04345023",
"company_name": "TESCO FREETIME LIMITED"
}
]
} POST /v1/enrich/jobs
Send up to 250,000 rows (or the key's own max_rows_per_job) and the requested blocks. The response is returned before the asynchronous job is complete. Poll the job, then page through its results.
| Parameter | In | Type | Description |
|---|---|---|---|
X-Idempotency-Key | header | string | This makes a submit safe to retry. The same key with the same request returns the original job (200); with a different request, 409. It is scoped to the account and kept with the job. |
Request body: JobCreateRequest, with rows (each with exactly one of company_number, company_name, domain, and an optional ref), blocks, and options (the same settings as the resolve query parameters, as JSON).
| Status | When | Body |
|---|---|---|
200 | A replay with the same X-Idempotency-Key and the same body. You get the original job and nothing new runs. | Job |
202 | The job was created. | Job |
400 | The request body is invalid. | FieldErrors |
401 | The key is missing, malformed, revoked or wrong. The message is the same in every case. | Error |
403 | The key lacks the enrich scope, no requested block is enabled, or a premium option is not enabled. | NotEnabledError | Error |
409 | The X-Idempotency-Key was used for a different body. | Error |
413 | The request contains more rows than the job or key allows. Split the file. | FieldErrors |
429 | The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After. | Error |
500 | The request failed on Zorro's side. The body carries request_id when available; the X-Request-ID header always does. | Error |
curl -X POST "https://api.getzorro.ai/v1/enrich/jobs" \
-H "Authorization: Bearer $ZORRO_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: docs-capture-2026-09-15-websites-1" \
-d '{
"rows": [
{
"ref": "crm-1001",
"company_number": "02805730"
},
{
"ref": "crm-1002",
"company_number": "08130873"
},
{
"ref": "crm-1003",
"company_number": "07706156"
}
],
"blocks": [
"domain",
"domain_validation"
],
"options": {
"validation": [
"rules",
"live"
]
}
}' Example response · 202 · jobs.create.202
{
"job_id": "d99ba955-f280-4723-b49a-fbb3dd872875",
"status": "queued",
"blocks": [
"domain",
"domain_validation"
],
"counts": {
"total": 3,
"processed": 0,
"succeeded": 0,
"not_found": 0,
"failed": 0,
"site_unreachable": 0
},
"delivery": {
"mode": "pull"
},
"results_ready": false,
"expires_at": null,
"created_at": "2026-09-15T07:07:47.705727Z",
"finished_at": null
} GET /v1/enrich/jobs/{job_id}
This call returns the job's state and progress counters. While the job has not finished, retry_after_ms says when to poll again.
| Parameter | In | Type | Description |
|---|---|---|---|
job_id | path | string | The job id from job creation or from a resolve. An id that is not yours, or not an id, answers 404. |
| Status | When | Body |
|---|---|---|
200 | The job is returned. | Job |
401 | The key is missing, malformed, revoked or wrong. The message is the same in every case. | Error |
403 | The key lacks the scope this endpoint needs; the message names it. | Error |
404 | There is no such job for this account. | Error |
429 | The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After. | Error |
500 | The request failed on Zorro's side. The body carries request_id when available; the X-Request-ID header always does. | Error |
curl "https://api.getzorro.ai/v1/enrich/jobs/d99ba955-f280-4723-b49a-fbb3dd872875" \
-H "Authorization: Bearer $ZORRO_API_KEY" Example response · 200 · jobs.status.queued.200
{
"job_id": "d99ba955-f280-4723-b49a-fbb3dd872875",
"status": "queued",
"blocks": [
"domain",
"domain_validation"
],
"counts": {
"total": 3,
"processed": 0,
"succeeded": 0,
"not_found": 0,
"failed": 0,
"site_unreachable": 0
},
"delivery": {
"mode": "pull"
},
"results_ready": false,
"expires_at": null,
"created_at": "2026-09-15T07:07:47.705727Z",
"finished_at": null,
"retry_after_ms": 1000
} Example response · 200 · jobs.status.completed.200
{
"job_id": "d99ba955-f280-4723-b49a-fbb3dd872875",
"status": "completed",
"blocks": [
"domain",
"domain_validation"
],
"counts": {
"total": 3,
"processed": 3,
"succeeded": 3,
"not_found": 0,
"failed": 0,
"site_unreachable": 0
},
"delivery": {
"mode": "pull"
},
"results_ready": true,
"expires_at": "2026-10-15T07:09:32.659195Z",
"created_at": "2026-09-15T07:07:47.705727Z",
"finished_at": "2026-09-15T07:09:32.659195Z"
} GET /v1/enrich/jobs/{job_id}/results
This call returns every row in submission order, whatever its status, one page at a time by cursor. A row that is still pending keeps its place in the page. It is also how you collect a defer_live resolve.
| Parameter | In | Type | Description |
|---|---|---|---|
job_id | path | string | The job id from job creation or from a resolve. An id that is not yours, or not an id, answers 404. |
cursor | query | string | Use next_cursor from the previous page. Omit it to start. |
limit | query | integer | The page size defaults to 100 and is at most 1000. A missing or invalid value uses the default. |
| Status | When | Body |
|---|---|---|
200 | A page is returned. | ResultsPage |
400 | The cursor is malformed. | Error |
401 | The key is missing, malformed, revoked or wrong. The message is the same in every case. | Error |
403 | The key lacks the scope this endpoint needs; the message names it. | Error |
404 | There is no such job for this account. | Error |
410 | The rows are no longer available (after expires_at). | Gone |
429 | The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After. | Error |
500 | The request failed on Zorro's side. The body carries request_id when available; the X-Request-ID header always does. | Error |
curl "https://api.getzorro.ai/v1/enrich/jobs/d99ba955-f280-4723-b49a-fbb3dd872875/results?limit=2" \
-H "Authorization: Bearer $ZORRO_API_KEY" Example response · 200 · jobs.results.page1.200
With limit=2, the page holds two rows and a next_cursor.
{
"results": [
{
"ref": "crm-1001",
"status": "done",
"blocks": {
"domain": {
"status": "available",
"value": "hotelchocolat.com",
"source": "cache",
"final_url": "https://www.hotelchocolat.com/uk"
},
"domain_validation": {
"status": "available",
"value": {
"domain": "hotelchocolat.com",
"dns_resolves": true,
"email_deliverable": true,
"site_live": true,
"http_status": 200,
"final_url": "https://www.hotelchocolat.com/uk",
"director_check": "companies_house",
"score": 100,
"outcome": "accept",
"checks": {
"legal_name_exact": 0.5,
"business_name": true,
"same_postcode": true,
"similar_postcode": true,
"city_match": true,
"county_match": true,
"address_match": true,
"director_match": false,
"company_number": true,
"other_company_number": false,
"foreign_registration": false,
"uk_domain": false,
"phone_present": true,
"generic_platform": false,
"parked": false
},
"contributions": {
"legal_name_exact": 15,
"business_name": 15,
"same_postcode": 25,
"similar_postcode": 8,
"city_match": 10,
"county_match": 5,
"address_match": 15,
"phone_present": 3,
"company_number": 50
},
"reasons": [
"all checks passed"
]
},
"source": "zorro_validator",
"confidence": 1,
"flagged_for_review": false,
"detail": {
"evidence": {
"hard_matches": {
"company_number": {
"url": "https://www.hotelchocolat.com/uk/i/terms-and-conditions.html",
"snippet": "…ngdom. Registered in England and Wales under company number 2805730. VAT number 945695766 (“Hotel Chocolat”) 2. Information Abo…"
},
"postcode": {
"url": "https://www.hotelchocolat.com/uk",
"snippet": "…pp © Hotel Chocolat 2026. Mint House, Newark Close, Royston SG8 5HL, UK An Affiliate of Mars, Incorporated Terms & Conditions S…"
},
"registered_address": {
"url": "https://www.hotelchocolat.com/uk",
"snippet": "…s Sign Up Follow Us Download Our App © Hotel Chocolat 2026. Mint House, Newark Close, Royston SG8 5HL, UK An Affiliate of Mars, In…"
}
},
"name_match": {
"kind": "trading_name",
"url": "https://www.hotelchocolat.com/uk",
"snippet": "Luxury Chocolates | Chocolate Gifts & Hampers | Hotel Chocolat Skip to Content Help Help & Support FREE Standard Delivery…"
}
}
}
}
},
"matched": {
"company_number": "02805730",
"company_name": "HOTEL CHOCOLAT LIMITED",
"match_confidence": 1,
"matched_on": "company_number"
}
},
{
"ref": "crm-1002",
"status": "done",
"blocks": {
"domain": {
"status": "available",
"value": "gymshark.com",
"source": "cache",
"final_url": "https://www.gymshark.com/"
},
"domain_validation": {
"status": "available",
"value": {
"domain": "gymshark.com",
"dns_resolves": true,
"email_deliverable": true,
"site_live": true,
"http_status": 200,
"final_url": "https://www.gymshark.com/",
"director_check": "companies_house",
"score": 95,
"outcome": "accept",
"checks": {
"legal_name_exact": true,
"business_name": true,
"same_postcode": false,
"similar_postcode": false,
"city_match": true,
"county_match": false,
"address_match": true,
"director_match": true,
"company_number": false,
"other_company_number": false,
"foreign_registration": false,
"uk_domain": false,
"phone_present": false,
"generic_platform": false,
"parked": false
},
"contributions": {
"legal_name_exact": 30,
"business_name": 15,
"city_match": 10,
"address_match": 15,
"director_match": 25
},
"reasons": [
"all checks passed"
]
},
"source": "zorro_validator",
"confidence": 0.95,
"flagged_for_review": false,
"detail": {
"evidence": {
"hard_matches": {
"director": {
"url": "https://www.gymshark.com/pages/about-us",
"snippet": "…e do today to prepare for tomorrow. Gymshark is led by: Ben Francis - Founder & Chief Executive Officer Noel Mack - Chief Brand…",
"surname": "francis"
}
},
"name_match": {
"kind": "legal_name",
"url": "https://www.gymshark.com/pages/terms-and-conditions",
"snippet": "…Language Selector dropdown English English Español © 2026 | Gymshark Limited | All Rights Reserved. | We Do Gym."
}
}
}
}
},
"matched": {
"company_number": "08130873",
"company_name": "GYMSHARK LTD",
"match_confidence": 1,
"matched_on": "company_number"
}
}
],
"next_cursor": "eyJzZXEiOjJ9",
"remaining": 1,
"results_ready": true,
"expires_at": "2026-10-15T07:09:32.659195Z"
} Example response · 200 · jobs.results.page2.200
This page was fetched with cursor set to the previous next_cursor; on the last page, next_cursor is null.
{
"results": [
{
"ref": "crm-1003",
"status": "done",
"blocks": {
"domain": {
"status": "available",
"value": "allica.bank",
"source": "cache",
"flagged_for_review": false,
"verification_profile": "verifiable",
"reason": "provenance_uncertain_but_validated",
"final_url": "https://www.allica.bank/"
},
"domain_validation": {
"status": "available",
"value": {
"domain": "allica.bank",
"dns_resolves": true,
"email_deliverable": true,
"site_live": true,
"http_status": 200,
"final_url": "https://www.allica.bank/",
"director_check": "companies_house",
"score": 100,
"outcome": "accept",
"checks": {
"legal_name_exact": true,
"business_name": true,
"same_postcode": true,
"similar_postcode": true,
"city_match": true,
"county_match": false,
"address_match": true,
"director_match": false,
"company_number": true,
"other_company_number": false,
"foreign_registration": false,
"uk_domain": false,
"phone_present": true,
"generic_platform": false,
"parked": false
},
"contributions": {
"legal_name_exact": 30,
"business_name": 15,
"same_postcode": 25,
"similar_postcode": 8,
"city_match": 10,
"address_match": 15,
"phone_present": 3,
"company_number": 50
},
"reasons": [
"all checks passed"
]
},
"source": "zorro_validator",
"confidence": 1,
"flagged_for_review": false,
"detail": {
"evidence": {
"hard_matches": {
"company_number": {
"url": "https://www.allica.bank/",
"snippet": "…2A 2DT. Registered in England and Wales with company number 07706156. Allica Bank savings accounts and business current account…"
},
"postcode": {
"url": "https://www.allica.bank/",
"snippet": "…Registered office: 4th/5th Floor, 15 Worship Street, London EC2A 2DT. Registered in England and Wales with company number 077061…"
},
"registered_address": {
"url": "https://www.allica.bank/",
"snippet": "…tial Regulation Authority (FRN: 821851). Registered office: 4th/5th Floor, 15 Worship Street, London EC2A 2DT. Registered in England and Wales with comp…"
}
},
"name_match": {
"kind": "legal_name",
"url": "https://www.allica.bank/",
"snippet": "…ints Tariff of fees Fraud advice Trustpilot Connect with us Allica Bank Limited is authorised by the Prudential Regulation Authority and re…"
}
}
}
}
},
"matched": {
"company_number": "07706156",
"company_name": "ALLICA BANK LIMITED",
"match_confidence": 1,
"matched_on": "company_number"
}
}
],
"next_cursor": null,
"remaining": 0,
"results_ready": true,
"expires_at": "2026-10-15T07:09:32.659195Z"
} GET /v1/enrich/jobs/{job_id}/errors
This call returns the rows that could not be resolved at all, each with a reason and whether a retry is worthwhile.
| Parameter | In | Type | Description |
|---|---|---|---|
job_id | path | string | The job id from job creation or from a resolve. An id that is not yours, or not an id, answers 404. |
cursor | query | string | Use next_cursor from the previous page. Omit it to start. |
limit | query | integer | The page size defaults to 100 and is at most 1000. A missing or invalid value uses the default. |
| Status | When | Body |
|---|---|---|
200 | A page is returned. | ErrorsPage |
400 | The cursor is malformed. | Error |
401 | The key is missing, malformed, revoked or wrong. The message is the same in every case. | Error |
403 | The key lacks the scope this endpoint needs; the message names it. | Error |
404 | There is no such job for this account. | Error |
410 | The rows are no longer available (after expires_at). | Gone |
429 | The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After. | Error |
500 | The request failed on Zorro's side. The body carries request_id when available; the X-Request-ID header always does. | Error |
curl "https://api.getzorro.ai/v1/enrich/jobs/d99ba955-f280-4723-b49a-fbb3dd872875/errors" \
-H "Authorization: Bearer $ZORRO_API_KEY" Example response · 200 · jobs.errors.200
{
"errors": [],
"next_cursor": null,
"retryable_count": 0,
"expires_at": "2026-10-15T07:09:32.659195Z"
} POST /v1/enrich/jobs/{job_id}/cancel
Rows that have started are delivered; rows that have not started are skipped and never billed. Cancelling a finished job returns it unchanged, so a retried cancel is safe.
| Parameter | In | Type | Description |
|---|---|---|---|
job_id | path | string | The job id from job creation or from a resolve. An id that is not yours, or not an id, answers 404. |
| Status | When | Body |
|---|---|---|
200 | The job is returned in its current state. | Job |
401 | The key is missing, malformed, revoked or wrong. The message is the same in every case. | Error |
403 | The key lacks the scope this endpoint needs; the message names it. | Error |
404 | There is no such job for this account. | Error |
429 | The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After. | Error |
500 | The request failed on Zorro's side. The body carries request_id when available; the X-Request-ID header always does. | Error |
curl -X POST "https://api.getzorro.ai/v1/enrich/jobs/d99ba955-f280-4723-b49a-fbb3dd872875/cancel" \
-H "Authorization: Bearer $ZORRO_API_KEY" Example response · 200 · jobs.cancel.200
Cancelling a job that already finished returns it unchanged; a retried cancel is safe.
{
"job_id": "d99ba955-f280-4723-b49a-fbb3dd872875",
"status": "completed",
"blocks": [
"domain",
"domain_validation"
],
"counts": {
"total": 3,
"processed": 3,
"succeeded": 3,
"not_found": 0,
"failed": 0,
"site_unreachable": 0
},
"delivery": {
"mode": "pull"
},
"results_ready": true,
"expires_at": "2026-10-15T07:09:32.659195Z",
"created_at": "2026-09-15T07:07:47.705727Z",
"finished_at": "2026-09-15T07:09:32.659195Z"
} GET /v1/usage
This call returns the counts your invoices are built from, over a date range (by default the last 30 days, at most 366). Until contract prices are set up for the account, it returns counts only.
| Parameter | In | Type | Description |
|---|---|---|---|
from | query | string | This is the first day, in YYYY-MM-DD format and UTC. |
to | query | string | This is the last day, inclusive. The default is today. |
granularity | query | "block" | "day" | Use block (default) or day. |
job_id | query | string | Narrow to one job. |
| Status | When | Body |
|---|---|---|
200 | Usage. | Usage |
400 | The request contains an invalid date, a from date later than the to date, a range over 366 days, or an unknown granularity. | Error |
401 | The key is missing, malformed, revoked or wrong. The message is the same in every case. | Error |
403 | The key lacks the scope this endpoint needs; the message names it. | Error |
404 | job_id is not a job on this account. | Error |
429 | The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After. | Error |
500 | The request failed on Zorro's side. The body carries request_id when available; the X-Request-ID header always does. | Error |
curl "https://api.getzorro.ai/v1/usage?job_id=d99ba955-f280-4723-b49a-fbb3dd872875" \
-H "Authorization: Bearer $ZORRO_API_KEY" Example response · 200 · usage.job.200
Until contract prices are set up for the account, usage shows counts only.
{
"range": {
"from": "2026-08-17",
"to": "2026-09-15"
},
"granularity": "block",
"pricing_status": "not_priced",
"pricing_note": "Counts only; amounts appear once contract prices are set.",
"blocks": [
{
"block": "domain",
"events": 3,
"billed_events": 3,
"outcomes": {
"delivered": 3
}
},
{
"block": "domain_validation",
"events": 3,
"billed_events": 3,
"outcomes": {
"delivered": 3
}
}
],
"totals": {
"events": 6,
"billed_events": 6,
"rows": 6,
"billed_rows": 6,
"invoiceable": false
},
"billing_rule": "Charged once per row per block, and only where the block delivered a value. not_found, ambiguous_match, not_enabled, provider errors and rows skipped by a cancel are recorded at zero.",
"job_id": "d99ba955-f280-4723-b49a-fbb3dd872875"
} Example response · 200 · usage.day.200
{
"range": {
"from": "2026-09-14",
"to": "2026-09-15"
},
"granularity": "day",
"pricing_status": "not_priced",
"pricing_note": "Counts only; amounts appear once contract prices are set.",
"days": [
{
"date": "2026-09-14",
"events": 0,
"billed_events": 0,
"outcomes": {},
"blocks": {}
},
{
"date": "2026-09-15",
"events": 6,
"billed_events": 6,
"outcomes": {
"delivered": 6
},
"blocks": {
"domain": 3,
"domain_validation": 3
}
}
],
"totals": {
"events": 6,
"billed_events": 6
},
"billing_rule": "Charged once per row per block, and only where the block delivered a value. not_found, ambiguous_match, not_enabled, provider errors and rows skipped by a cancel are recorded at zero.",
"job_id": "d99ba955-f280-4723-b49a-fbb3dd872875"
} Blocks
A block is one kind of answer about a company. GET /v1/blocks returns the catalogue: 37 blocks with descriptions, sources and which ones your key may request. A block that is not enabled is still listed, so your code can see which blocks exist and you can ask us to switch one on.
Tier sets how a block is priced. Core covers register data and the website check; Premium covers the remaining blocks. Latency is
instant (answered in the first response) or live (checked at
request time; pending under defer_live). Default
says whether the block is on for a new account or switched on per account.
| Block | Tier | Source | Latency | Default |
|---|---|---|---|---|
company_identity | Core | Companies House register, Third-party data | instant | On |
registered_address | Core | Companies House register | instant | On |
officers | Core | Companies House register, Companies House | instant | On |
controllers | Core | Companies House register, Companies House | instant | On |
corporate_owners | Core | Companies House register, Companies House, Company website | instant | On |
ultimate_owner | Premium | Companies House register, Derived by Zorro | instant | On |
charges | Core | Companies House register, Companies House | instant | On |
industry | Core | Companies House register, Derived by Zorro, Company website | instant | On |
filing_status | Core | Companies House register, Companies House | instant | On |
registry_events | Core | Companies House register | instant | On |
financials_filed | Premium | Filed accounts, Modelled by Zorro, PDF accounts | instant | On |
employees_filed | Premium | Filed accounts, PDF accounts | instant | On |
domain | Core | Company website | instant | On |
domain_validation | Core | Company website, Companies House register | live | On |
nature_of_business | Premium | Company website, Company website | instant | On |
subsidiaries | Premium | Companies House register, Derived by Zorro | instant | On request |
property | Premium | HM Land Registry | instant | On |
related_company_domain | Premium | Companies House register, Company website | instant | On |
contacts | Premium | Companies House register, Third-party data | live | On |
financials_live | Premium | Companies House, PDF accounts | live | On request |
financial_benchmarks | Premium | Derived by Zorro, Filed accounts | live | On request |
trading_address | Premium | Company website, Other public registers | live | On request |
revenue_estimate | Premium | Modelled by Zorro | instant | On request |
scores | Premium | Modelled by Zorro | instant | On request |
comparables | Premium | Derived by Zorro | live | On request |
employees | Premium | Filed accounts, Third-party data | instant | On request |
employees_live | Premium | Third-party profile data | instant | On request |
firmographics | Premium | Third-party profile data | instant | On request |
web_traffic | Premium | Third-party data | instant | On request |
web_presence | Premium | Third-party data | instant | On request |
technologies | Premium | Third-party data | instant | On request |
funding | Premium | Third-party data | live | On request |
signals | Premium | Companies House register, Third-party data | live | On request |
news_signals | Premium | Third-party data | instant | On request |
planning | Premium | Other public registers | instant | On request |
business_intelligence | Premium | Company website, Company website | instant | On request |
company_summary | Premium | Company website | instant | On request |
The blocks used for call preparation and website enrichment are described field by field below. Types come
from the OpenAPI value schemas; keys that do not apply to a company are absent, not
null, unless the type says null. Every block may also answer
not_found or error with one of the reasons listed.
Core
company_identity
This block returns the incorporation date, category, legal form, status as filed (with a status group) and previous names. The trading name comes from a company profile rather than Companies House, and the block says so.
| value. | Type | Description |
|---|---|---|
incorporated_on | string | Incorporation date. YYYY-MM-DD. |
company_category | string | This is the register's category, e.g. Private Limited Company. |
country_of_origin | string | This is shown as filed. |
legal_form | string | |
company_status | string | The register's label verbatim: Active, Dissolved, Liquidation, Active - Proposal to Strike off, ... |
status_group | "active" | "proposal_to_strike_off" | "insolvency" | "dissolved" | "closed" | "other" | |
dissolved_on | string | Where a dissolution date is on file. YYYY-MM-DD. |
trading_name | string | From a company profile field, not Companies House (which records none). |
trading_name_source | "third_party_profile" | |
other_trading_names | string[] | Further distinct trading names from the same profile fields. |
trading_name_withheld | string | Why a profile name was not served (it names another organisation). |
previous_names | object[] |
registered_address
This block returns the registered office as filed, with only the lines the register holds.
| value. | Type | Description |
|---|---|---|
address_line_1 | string | |
address_line_2 | string | |
locality | string | This is the post town. |
region | string | This is the county. |
postal_code | string | |
country | string | |
po_box | string | |
care_of | string |
officers
This block returns officer counts, an age spread, current officers by name (at most 25) and resigned officers (at most 50), with role, dates, nationality, country of residence, occupation, person number and month and year of birth. The block does not include the day of birth or an address.
| value. | Type | Description |
|---|---|---|
total | integer | null | This is the number of officers ever appointed. |
active | integer | null | This is the number of current officers. |
resigned | integer | This is the number of resigned officers. |
current | Officer[] | Current officers, directors first, at most 25. Each item: name, role, appointed_on, resigned_on, nationality, country_of_residence, occupation, person_number, person_key, date_of_birth, is_corporate. |
former | Officer[] | Resigned officers, newest resignation first, at most 50. Each item: name, role, appointed_on, resigned_on, nationality, country_of_residence, occupation, person_number, person_key, date_of_birth, is_corporate. |
former_truncated | boolean | former[] was cut at 50. |
ages | object |
controllers
This block lists everyone with significant control, typed as company, person or statement, with natures of control and dates. Ceased entries are in a separate list.
| value. | Type | Description |
|---|---|---|
controllers | Controller[] | Live PSC entries; ceased ones are excluded. Each item: type, name, company_number, country_registered, nationality, country_of_residence, date_of_birth, natures_of_control, notified_on, statement, ceased_on. |
has_corporate_owner | boolean | A company or legal entity is among the live entries. |
control | object | |
ceased_controllers | Controller[] | Ceased entries, newest first, each with ceased_on. Each item: type, name, company_number, country_registered, nationality, country_of_residence, date_of_birth, natures_of_control, notified_on, statement, ceased_on. |
corporate_owners
This block lists the companies and legal entities that have significant control now. An empty list means the register names no corporate owner. Each owner may carry its own nature_of_business envelope.
| value. | Type | Description |
|---|---|---|
has_corporate_owner | boolean | |
owners | object[] |
industry
This block returns every SIC 2007 code filed, primary first, with Zorro's label and the NACE class. Activity tags from the website are included only for a validated website.
| value. | Type | Description |
|---|---|---|
sic | object[] | |
label | string | This is a plain-English industry label. |
label_source | "sic" | "nace" | "unknown" | "other" | |
nace | object[] | |
tags | object | These are activity tags from the website; they are withheld when the website is not validated. |
tags_source | "zorro_from_website" | |
sic_is_generic | boolean | None of the filed codes names a trade (dormant, holding, n.e.c.). |
declared_activity | string | This is the nature_of_business one-liner for comparison when that block is on the same row. |
charges
This block returns outstanding charges and their holders exactly as filed, with charge counts. Satisfied charges and past holders are listed separately.
| value. | Type | Description |
|---|---|---|
outstanding_count | integer | Counted from the charge records. |
satisfied_count | integer | |
total_count | integer | |
unclassified_count | integer | This is the number of charges with no classification. |
holders | string[] | These are the distinct holders of outstanding charges, shown verbatim. |
charges | Charge[] | Outstanding charges. Each item: charge_code, status, created_on, registered_on, satisfied_on, persons_entitled, type, particulars, contains, secured_on, amount_secured. |
satisfied_charges | Charge[] | Newest satisfaction first, at most 50. Each item: charge_code, status, created_on, registered_on, satisfied_on, persons_entitled, type, particulars, contains, secured_on, amount_secured. |
satisfied_truncated | boolean | |
past_holders | string[] | These are the holders of satisfied charges. |
latest_created_on | string | |
latest_satisfied_on | string |
filing_status
This block returns when the accounts and the confirmation statement were last made up and are next due, with the accounts category and the overdue flags.
| value. | Type | Description |
|---|---|---|
accounts_last_made_up_on | string | YYYY-MM-DD. |
accounts_next_due_on | string | YYYY-MM-DD. |
accounts_next_made_up_to | string | YYYY-MM-DD. |
accounts_category | string | This is the register's accounts category, e.g. FULL, SMALL, MICRO ENTITY, NO ACCOUNTS FILED. |
accounts_overdue | boolean | The register's overdue flag. |
confirmation_statement_last_made_up_on | string | YYYY-MM-DD. |
confirmation_statement_next_due_on | string | YYYY-MM-DD. |
confirmation_statement_overdue | boolean | The register's overdue flag. |
accounts_last_made_up_on_expected | object | This is given with reason accounts_made_up_date_off_reference_date and contains the date implied by the accounting reference date. The date above is left unchanged. |
corrections | object | This is a corrected due date because accounts on the register had already met the earlier date. |
domain
This block returns the registrable domain of the company's own website, never a related company's site.
value is a string.
domain_validation
This block reports whether the website belongs to the company. It returns twelve check results, a score, an outcome (accept, review or reject) and the evidence.
| value. | Type | Description |
|---|---|---|
domain | string | This is the website checked or, after a passing redirect, the final domain. |
dns_resolves | boolean | null | In live mode, the name resolves. null means that the check could not be completed. |
email_deliverable | boolean | null | In live mode, the domain accepts mail. null means that the check could not be completed. |
site_live | boolean | null | In live mode, the site answered over HTTP(S). |
http_status | integer | null | This is the HTTP status returned for the website. |
final_url | string | This is the final URL after redirects. |
final_domain | string | The domain that the domain on file forwards to. |
note | string | |
director_check | "companies_house" | "source_busy" | This identifies the source of the director check. |
score | integer | This is the score, with a maximum of 100. |
outcome | "accept" | "review" | "reject" | The verdict. accept: score >= 50 with a hard match and no objection. review: 40–49, or >= 50 without a hard match. reject: under 40. |
reason | string | name_matches_no_address_evidence when only the name tied the site to the company. |
checks | object | This object may contain legal_name_exact, business_name, same_postcode, similar_postcode, city_match, county_match, address_match, director_match, company_number, other_company_number, foreign_registration, uk_domain, phone_present, generic_platform and parked. |
contributions | object | This object contains the contribution associated with each check. |
reasons | string[] | These are short notes about the checks. |
ai_outcome | "pass" | "fail" | ai mode: reported beside the score; it does not change the score. |
ai_reason | string |
Premium
ultimate_owner
This block returns the company at the top of the ownership chain, as Zorro resolves it from PSC filings, with the chain and its depth.
| value. | Type | Description |
|---|---|---|
company_name | string | null | |
company_number | string | null | |
depth | integer | This is the number of steps from this company to the top. |
resolved_by | string | This is the resolution category for the top of the chain. |
chain | object[] |
financials_filed
This block returns the accounts history, newest first. Filed and modelled figures are in separate objects, figures read from PDF accounts are included where available, and each ratio names its inputs. Headline figures carry a *_basis field.
| value. | Type | Description |
|---|---|---|
revenue_gbp | number | This is the newest filed or ocr_read turnover, or the modelled turnover only when nothing was filed. Check revenue_basis. |
revenue_basis | string | The value is filed, ocr_read or modelled. |
revenue_period_end | string | |
profit_loss_gbp | number | This is the headline profit and follows the same rule. |
profit_loss_basis | string | |
profit_loss_period_end | string | |
turnover_change_pct | number | |
turnover_change_basis | string | |
turnover_change_period_end | string | |
best_turnover_growth_pct | number | |
best_turnover_growth_basis | string | |
profit_margin_stable | boolean | |
filed_revenue | object | |
filed_profit_loss | object | |
modelled_profit_loss | object | |
modelled_revenue | object | This is the modelled revenue with its band. |
modelled_revenue_withheld | ReasonCode | This lists every reason the API may give on a block envelope, a row or an error body. Any reason outside the published list is returned as error. New codes may be added within /v1, so treat a code you do not recognise as error. |
turnover_definition | object | |
withheld | object[] | These are withheld figures, each with period_end, side, field and reason. |
years | integer | This is the number of periods. |
periods | FinancialPeriod[] | Newest first. Each item: year, period_start, period_end, period_end_basis, filed, modelled, ocr_read, derived, not_derivable, filed_withheld, modelled_withheld, flagged_for_review. |
employees_filed
This block returns the average number of employees as filed, with the year. When nothing was filed, the block may contain a flagged, derived figure from PDF accounts.
value is an integer.
nature_of_business
This block describes what the company does, based on its website. derived_from says which description was used.
| value. | Type | Description |
|---|---|---|
one_liner | string | This is a short description. |
product_service | string | This describes the offering in detail. |
subsidiaries
This block lists the companies whose own PSC register names this company as a controller. It covers direct holdings only, with natures of control and an ownership band.
| value. | Type | Description |
|---|---|---|
subsidiaries | object[] | |
count | integer | |
active_count | integer | |
truncated | boolean | This is true when there are more than 100 matching companies. |
property
This block lists the freehold and leasehold titles matched on company number, with postcodes, addresses and the total price paid (not a valuation).
| value. | Type | Description |
|---|---|---|
title_count | integer | null | |
total_price_paid_gbp | number | The sum paid for the titles when bought. Not a valuation. |
tenure | string[] | |
postcodes | string[] | |
addresses | string[] |
The other blocks share the same response format; their values are described in
GET /v1/blocks and carried as an open object in the OpenAPI description.
Response format
Every block comes back in the same envelope, whether from a resolve or from a job row. Keys that do not apply are absent rather than null:
| Key | Type | Meaning |
|---|---|---|
status | BlockStatus | available: value is present. not_found: there is no value; reason says what kind of absence it is. not_enabled: the key may not run this block. error: the block failed on Zorro's side, usually with retry_suggested. pending: a progressive resolve (defer_live) is still working on it. |
value | any | This is present only when status is available. Its shape depends on the block; see the block schemas. |
source | string | Where the value came from: companies_house, companies_house_live, companies_house_psc, companies_house_accounts, companies_house_accounts_ocr, zorro_modelled_from_filings, hm_land_registry, cache, live_site_read, zorro_validator, zorro_from_website, zorro_peer_model, third_party_profile and others named in the catalogue. |
as_of | string | The date of the information: a date (YYYY-MM-DD), a year (YYYY) for a filing year, or the time the value was checked. Absent when no date exists. |
confidence | number | A number from 0 to 1, present only on blocks that produce one. The scale differs between blocks; see confidence in GET /v1/blocks. |
flagged_for_review | boolean | When true, read reason before relying on the value. The value is still returned. |
reason | ReasonCode | This lists every reason the API may give on a block envelope, a row or an error body. Any reason outside the published list is returned as error. New codes may be added within /v1, so treat a code you do not recognise as error. |
detail | EnvelopeDetail | How the answer was built. Blocks can add their own keys. |
partial | object | For not_found or error, this contains available information, such as the domain and whether the site answered. |
retry_suggested | boolean | This indicates that the block was not completed or confirmed for the request. |
derived | boolean | The value is a Zorro-derived value rather than a filed fact. |
derived_from | "company_website_short" | "company_website_detailed" | "search_description" | "company_website" | "zorro_enrichment" | For nature_of_business, this identifies the associated description. |
contains_modelled | boolean | financials_filed: part of the value is modelled (under modelled, or with a *_basis of modelled). |
contains_ocr_read | boolean | For financials_filed, part of the value was read from PDF accounts. |
contains_derived | boolean | For financials_filed, ratios computed from filed figures or figures read from PDF accounts are present. |
host_type | "register" | "directory" | "social" | "site_builder" | |
verification_profile | string | For domain, this contains website verification information. |
final_url | string | domain: the address actually read after redirects, when a block on the row opened the site. |
affected_years | string[] | For employees_filed with filed_value_implausible, these are the years whose filed figures were refused. |
Examples
The following example shows an available block:
{
"status": "available",
"value": {
"title_count": 6,
"tenure": [
"Freehold",
"Leasehold"
],
"postcodes": [
"PE29 7HA",
"PE19 8JJ",
"PE29 7DQ"
],
"addresses": [
"Land lying to the west of Sallowbush Road, Huntingdon",
"3 Redwongs Way, Huntingdon, (PE29 7HA)",
"1a and, 2b Alpha Drive, Eaton Socon, St Neots (PE19 8JJ)",
"Topper Cases Ltd, 1 Glebe Road, Huntingdon (PE29 7DQ)",
"Land lying to the west of Sallowbush Road, Huntingdon",
"Unit SU31, Block E/F, Southgate, Bath"
]
},
"source": "hm_land_registry"
} The following example shows an absent block with its reason and website verdict:
{
"status": "not_found",
"reason": "no_description_on_file",
"detail": {
"website_verdict": {
"domain": "hotelchocolat.com",
"outcome": "accept",
"score": 100,
"confidence": 1,
"checked_at": "2026-09-15T07:07:48Z",
"auto_run": false,
"served": false
}
}
} The following example shows a block that the key may not request:
{
"status": "not_enabled",
"reason": "no_entitlement"
} A full row is under Quick start; each endpoint below has its own example responses.
Jobs and progressive answers
Resolve: synchronous, progressive or deferred
| Call | What you get | Use it for |
|---|---|---|
GET /v1/resolve | The response contains the whole row after every block has returned a result. | Use this for blocks returned in the first response. |
…&defer_live=true | The response contains available blocks and marks the remaining blocks as pending. While the row is incomplete, the response also carries complete, pending_blocks, results_url, status_url and retry_after_ms. | Use this for blocks that open a website or check a live source. |
…&wait=false | The response is a 202 with the job and results_url. It does not contain block results. | Use this for calls that you would rather poll than wait for. |
defer_live=true cannot be combined with wait=false or store=false; the request returns 400. A pending block does not contain a placeholder value. A live block that cannot finish answers error with
retry_suggested, and the row completes without it. A resolve is billed
once, when the row is complete.
The following example shows a poll while domain_validation is still pending:
{
"results": [
{
"ref": "",
"status": "pending",
"blocks": {
"domain": {
"status": "available",
"value": "hotelchocolat.com",
"source": "cache"
},
"domain_validation": {
"status": "pending"
},
"nature_of_business": {
"status": "not_found",
"reason": "no_description_on_file",
"detail": {
"website_verdict": {
"domain": "hotelchocolat.com",
"outcome": "accept",
"score": 100,
"confidence": 1,
"checked_at": "2026-09-15T07:07:48Z",
"auto_run": false,
"served": false
}
}
},
"property": {
"status": "available",
"value": {
"title_count": 6,
"tenure": [
"Freehold",
"Leasehold"
],
"postcodes": [
"PE29 7HA",
"PE19 8JJ",
"PE29 7DQ"
],
"addresses": [
"Land lying to the west of Sallowbush Road, Huntingdon",
"3 Redwongs Way, Huntingdon, (PE29 7HA)",
"1a and, 2b Alpha Drive, Eaton Socon, St Neots (PE19 8JJ)",
"Topper Cases Ltd, 1 Glebe Road, Huntingdon (PE29 7DQ)",
"Land lying to the west of Sallowbush Road, Huntingdon",
"Unit SU31, Block E/F, Southgate, Bath"
]
},
"source": "hm_land_registry"
},
"subsidiaries": {
"status": "available",
"value": {
"subsidiaries": [
{
"company_number": "10487072",
"company_name": "RABOT 1745 LIMITED",
"company_status": "Dissolved",
"natures_of_control": [
"ownership-of-shares-75-to-100-percent",
"voting-rights-75-to-100-percent"
],
"ownership_band": "75–100%",
"notified_on": "2017-10-30",
"matched_on": "registration_number"
},
{
"company_number": "02174370",
"company_name": "HOTEL CHOCOLAT CORPORATE LTD",
"company_status": "Dissolved",
"natures_of_control": [
"ownership-of-shares-75-to-100-percent",
"voting-rights-75-to-100-percent"
],
"ownership_band": "75–100%",
"notified_on": "2016-04-06",
"matched_on": "registration_number"
}
],
"count": 2,
"active_count": 0
},
"source": "companies_house_psc",
"as_of": "2017-10-30"
}
},
"pending_blocks": [
"domain_validation"
],
"matched": {
"company_number": "02805730",
"company_name": "HOTEL CHOCOLAT LIMITED",
"match_confidence": 1,
"matched_on": "company_number"
}
}
],
"next_cursor": null,
"remaining": 0,
"results_ready": false,
"expires_at": null,
"retry_after_ms": 1000
} Submit
POST /v1/enrich/jobs with rows, blocks and options returns 202 with the job. Send an
X-Idempotency-Key so a retried submit returns the same job. Large jobs run
asynchronously; poll the job for progress.
Poll
GET /v1/enrich/jobs/{job_id} returns the job. Wait
retry_after_ms between polls, and stop when results_ready is true. counts.processed increases as rows finish. There is no push delivery. options.delivery accepts webhook and a callback_url, but no callback is sent, so poll the job instead.
| Status | Meaning | Terminal |
|---|---|---|
queued | Accepted and in progress. counts.processed increases as rows finish. | No |
completed | Every row has settled and none has failed (rows may be not_found). | Yes |
partially_completed | Every row has settled, and at least one has failed. The failed rows are on the errors page. | Yes |
failed | The job could not finish; failure_reason says why. | Yes |
cancelling | A cancel is in progress. | No |
cancelled | Rows already in progress are finished and delivered; rows not yet started are skipped and not billed. | Yes |
running | This status is reserved. | No |
| Job field | Type | Meaning |
|---|---|---|
job_id | string | |
status | JobStatus | A job is queued until every row is settled, then completed, or partially_completed when at least one row failed. A cancel moves it through cancelling to cancelled. The job is failed when it could not finish; failure_reason says why. running is reserved. The terminal states are completed, partially_completed, cancelled and failed. |
blocks | BlockKey[] | A block in the catalogue. GET /v1/blocks lists all 37 and shows which ones your key may request. |
counts | JobCounts | |
delivery | object | |
results_ready | boolean | The job is terminal: every row is final. |
expires_at | string | null | When the job's rows stop being available. It is null while the job is running. |
created_at | string | |
finished_at | string | null | |
warnings | Warning[] | Some requested blocks are not enabled for the key; they come back not_enabled and every other block is unaffected. |
retry_after_ms | integer | For a job that is not terminal, this is the polling delay in milliseconds. |
results_url | string | This is present only on a job created by GET /v1/resolve?wait=false. |
Results
GET /v1/enrich/jobs/{job_id}/results returns every row, page by page, in the order you sent the rows and whatever their status, so a row still pending keeps its place.
Pass next_cursor back as ?cursor=; it is null on the
last page. The row statuses are pending, done (at least one block is available), not_found (matched, with nothing on file), error (see the errors page) and skipped (cancelled before it ran).
counts.site_unreachable counts rows whose website would not open.
curl "https://api.getzorro.ai/v1/enrich/jobs/d99ba955-f280-4723-b49a-fbb3dd872875/results?limit=2" \
-H "Authorization: Bearer $ZORRO_API_KEY" Errors
GET /v1/enrich/jobs/{job_id}/errors pages through rows that could not be
resolved at all, with the company number, name or domain you sent, a reason, retryable and, for an ambiguous name, the candidates. A row-level error is reported on this endpoint rather than as a job failure.
curl "https://api.getzorro.ai/v1/enrich/jobs/d99ba955-f280-4723-b49a-fbb3dd872875/errors" \
-H "Authorization: Bearer $ZORRO_API_KEY" {
"errors": [],
"next_cursor": null,
"retryable_count": 0,
"expires_at": "2026-10-15T07:09:32.659195Z"
} Cancel
POST /v1/enrich/jobs/{job_id}/cancel needs the enrich
scope. Cancelling a job that has already finished returns it unchanged, so a retried
cancel is safe.
Website check
domain_validation checks whether a website belongs to the company. The block comes back available with a score (0–100), a confidence (the score divided by 100) and an outcome:
| Outcome | Meaning |
|---|---|
accept | The site carries this company's registered identity, with at least one hard match and no objection. |
review | There is some evidence, but not enough to confirm the site. Treat it as unconfirmed. |
reject | The site is reachable, but nothing ties it to this company. |
A hard match is any of these found on the site: the registered postcode or its
district, the registered town, the street address, a current director's name, or the company number.
An objection is a page saying its company is registered outside the UK,
or printing another company's number as its own. The checks and the points each earned
are returned in checks and contributions, and
detail.evidence names the page and the words behind each hard match.
min_confidence
outcome is the verdict on the site. min_confidence (default 0.5, so sending 0.5 is the same as sending nothing) decides what happens next. The same reason appears on domain_validation and on every website block:
| Outcome | Against min_confidence | reason | Website-derived blocks |
|---|---|---|---|
| accept | at or above | none, flagged_for_review: false | served |
| review | at or above | website_needs_review | served, flagged |
| accept or review | below | website_not_validated | withheld |
| reject | any | website_not_validated | withheld |
| no score | Not applicable | website_unverified or website_not_verifiable | flagged, or withheld when the site could not be read at all |
When the site cannot be scored
site_unreachable: the site gave no HTTP response at all.site_blocked: the site answered and refused the request.retry_suggestedis set.site_not_readable: the site answered with no readable text.
Each reason returns not_found and flagged, with available information under partial. not_own_site means the domain is a register,
directory, social or site-builder host (host_type says which).
null on dns_resolves, email_deliverable or
site_live means "could not be checked", never "no".
Validation modes
validation (a query parameter, or options.validation in a job) controls what the check covers. The default is rules,live:
| Mode | What it adds |
|---|---|
rules | This mode compares the site with the company file and returns checks, contributions, score and outcome. |
live | This mode checks whether the site answered over HTTP(S) (site_live, http_status, final_url), whether the domain resolves (dns_resolves) and whether it accepts email (email_deliverable). |
officers | This mode compares director names with the Companies House officer list. director_check identifies the source of the director check. |
ai | This mode checks whether the site's business matches the registered SIC code and reports the result as ai_outcome and ai_reason beside the score; it does not change the score. |
none | Runs no check. The domain comes back unscored and flagged, with reason: validation_not_requested. |
A block built from the website (for example nature_of_business) uses the
verdict on that website. If you did not request domain_validation, the check may still run for that block without appearing in the response; detail.website_verdict.auto_run shows when it did.
Markets stated on the website
nature_of_business.detail.markets_stated is experimental. It lists the countries the company's own website states it serves (country sites or pages, shipping lists, offices, customers, currencies or languages offered), each with a basis: country_site, shipping, office, customers or currency_or_language. scope is worldwide when the site says it serves customers worldwide, and countries is then empty. This is not trade data. The field is absent when the site states none.
Errors and retries
HTTP errors
Every error body is JSON and includes detail. Branch on the HTTP status and on the codes in the body, never on the wording of a message.
| Status | When | Body |
|---|---|---|
200 | The request succeeded, including a replayed job submission (same X-Idempotency-Key and same body). | The resource |
202 | A job was created (POST /v1/enrich/jobs, or GET /v1/resolve with wait=false). | Jobexample |
400 | The input is invalid, for example two identifiers, a flag pair that cannot combine, an unknown block, an out-of-range option, a malformed company number or cursor, or a bad usage range. | Error or FieldErrorsexample |
401 | The key is missing, malformed, revoked or wrong. The message is the same for every case. | Errorexample |
403 | The key lacks the endpoint's scope, none of the requested blocks is enabled for the key, or a Premium option is not enabled. | NotEnabledError or Errorexample |
404 | GET /v1/resolve matched no company (detail is not_matched, sometimes with a reason). On other endpoints, the job id is not yours or does not exist. | ResolveNotFound or Errorexample |
409 | GET /v1/resolve matched more than one live company (the candidates are included), or an X-Idempotency-Key was reused with a different request. | AmbiguousMatch or Errorexample |
410 | The job's results or errors were requested after its expires_at, and the rows are no longer available. The job itself still answers. | Gone |
413 | The job has more rows than allowed (250,000, or the key's own max_rows_per_job). | FieldErrors under rows |
429 | The key sent more requests than its per-minute allowance. | Error, with a Retry-After headerexample |
500 | An error on Zorro's side. The body is always JSON and carries request_id when available. | Error |
502 | GET /v1/resolve could not resolve the row (detail is a row reason such as resolver_error). | Error |
202 · jobs.create.202
curl -X POST "https://api.getzorro.ai/v1/enrich/jobs" \
-H "Authorization: Bearer $ZORRO_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: docs-capture-2026-09-15-websites-1" \
-d '{
"rows": [
{
"ref": "crm-1001",
"company_number": "02805730"
},
{
"ref": "crm-1002",
"company_number": "08130873"
},
{
"ref": "crm-1003",
"company_number": "07706156"
}
],
"blocks": [
"domain",
"domain_validation"
],
"options": {
"validation": [
"rules",
"live"
]
}
}'
HTTP 202
{
"job_id": "d99ba955-f280-4723-b49a-fbb3dd872875",
"status": "queued",
"blocks": [
"domain",
"domain_validation"
],
"counts": {
"total": 3,
"processed": 0,
"succeeded": 0,
"not_found": 0,
"failed": 0,
"site_unreachable": 0
},
"delivery": {
"mode": "pull"
},
"results_ready": false,
"expires_at": null,
"created_at": "2026-09-15T07:07:47.705727Z",
"finished_at": null
} 400 · resolve.400.unknown_block
curl "https://api.getzorro.ai/v1/resolve?company_number=02805730&blocks=company_identity,not_a_block&defer_live=true" \
-H "Authorization: Bearer $ZORRO_API_KEY"
HTTP 400
{
"blocks": [
"Unknown block(s): not_a_block. Available: domain, related_company_domain, domain_validation, nature_of_business, registered_address, corporate_owners, controllers, charges, property, officers, company_identity, filing_status, registry_events, financials_filed, ultimate_owner, employees_filed, industry, contacts."
]
} 401 · whoami.401.invalid
curl "https://api.getzorro.ai/v1/whoami" \
-H "Authorization: Bearer $ZORRO_API_KEY"
HTTP 401
{
"detail": "Invalid API key."
} 403 · resolve.403.not_enabled
curl "https://api.getzorro.ai/v1/resolve?company_number=02805730&blocks=company_summary&defer_live=true" \
-H "Authorization: Bearer $ZORRO_API_KEY"
HTTP 403
{
"detail": "Not enabled for this account: company_summary.",
"blocks": {
"company_summary": {
"status": "not_enabled",
"reason": "no_entitlement"
}
}
} 404 · resolve.404.not_matched
curl "https://api.getzorro.ai/v1/resolve?company_number=00000000&blocks=company_identity&defer_live=true" \
-H "Authorization: Bearer $ZORRO_API_KEY"
HTTP 404
{
"detail": "not_matched"
} 409 · resolve.409.ambiguous
curl "https://api.getzorro.ai/v1/resolve?company_name=Tesco&blocks=company_identity&defer_live=true" \
-H "Authorization: Bearer $ZORRO_API_KEY"
HTTP 409
{
"detail": "ambiguous_match",
"candidates": [
{
"company_number": "00445790",
"company_name": "TESCO PLC"
},
{
"company_number": "00519500",
"company_name": "TESCO STORES LIMITED"
},
{
"company_number": "08730224",
"company_name": "TESCO SECRETARIES LIMITED"
},
{
"company_number": "00243011",
"company_name": "TESCO HOLDINGS LIMITED"
},
{
"company_number": "04345023",
"company_name": "TESCO FREETIME LIMITED"
}
]
} 429 · resolve.429
curl "https://api.getzorro.ai/v1/resolve?domain=shawbrook.co.uk&validation=rules,live&defer_live=true" \
-H "Authorization: Bearer $ZORRO_API_KEY"
HTTP 429
{
"detail": "Request was throttled. Expected available in 1 second."
} More 400, 404 and 409 bodies
curl "https://api.getzorro.ai/v1/resolve?company_number=02805730&company_name=Hotel%20Chocolat&defer_live=true" \
-H "Authorization: Bearer $ZORRO_API_KEY"
HTTP 400
{
"detail": "Give exactly one of company_number, company_name, or domain."
}curl "https://api.getzorro.ai/v1/resolve?company_number=07706156&blocks=company_identity&min_confidence=2" \
-H "Authorization: Bearer $ZORRO_API_KEY"
HTTP 400
{
"options": {
"min_confidence": [
"Ensure this value is less than or equal to 1.0."
]
}
}curl "https://api.getzorro.ai/v1/resolve?company_number=07706156&blocks=domain_validation&validation=rules,psychic" \
-H "Authorization: Bearer $ZORRO_API_KEY"
HTTP 400
{
"options": {
"validation": {
"1": [
"\"psychic\" is not a valid choice."
]
}
}
}curl "https://api.getzorro.ai/v1/resolve?company_number=07706156&blocks=company_identity&defer_live=true&store=false" \
-H "Authorization: Bearer $ZORRO_API_KEY"
HTTP 400
{
"detail": "defer_live=true cannot be combined with store=false: the live blocks are delivered through the stored job, so it has to be kept until you collect them."
}curl "https://api.getzorro.ai/v1/enrich/jobs/d99ba955-f280-4723-b49a-fbb3dd872875/results?cursor=not-a-cursor" \
-H "Authorization: Bearer $ZORRO_API_KEY"
HTTP 400
{
"detail": "Malformed cursor. Use the next_cursor from the previous page, or omit it to start."
}curl "https://api.getzorro.ai/v1/resolve?domain=find-and-update.company-information.service.gov.uk&blocks=company_identity" \
-H "Authorization: Bearer $ZORRO_API_KEY"
HTTP 404
{
"detail": "not_matched",
"reason": "not_own_site",
"host_type": "register"
}curl "https://api.getzorro.ai/v1/resolve?domain=zzqq-not-a-real-company-2026.co.uk&blocks=company_identity" \
-H "Authorization: Bearer $ZORRO_API_KEY"
HTTP 404
{
"detail": "not_matched",
"reason": "site_unreachable",
"site_lookup": {
"reason": "site_unreachable",
"domain": "zzqq-not-a-real-company-2026.co.uk",
"final_url": null,
"pages_read": []
}
}curl -X POST "https://api.getzorro.ai/v1/enrich/jobs" \
-H "Authorization: Bearer $ZORRO_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: docs-capture-2026-09-15-websites-1" \
-d '{
"rows": [
{
"ref": "crm-1001",
"company_number": "02805730"
},
{
"ref": "crm-1002",
"company_number": "08130873"
},
{
"ref": "crm-1003",
"company_number": "07706156"
}
],
"blocks": [
"domain"
],
"options": {
"validation": [
"rules",
"live"
]
}
}'
HTTP 409
{
"detail": "This idempotency key was already used for a different request. Use a new key, or resend the original body."
} GET /v1/resolve returns row reasons as an HTTP status: 404 for
not_matched, 409 for ambiguous_match,
400 for no_company_key and a malformed company number,
502 for the rest. In a job, the same row goes to the errors page.
Block statuses
| status | Meaning |
|---|---|
available | The value is present. Read flagged_for_review and reason when set. |
not_found | There is no value for this company; reason identifies the type of absence. |
not_enabled | The key may not run this block. |
error | The block could not be answered for this request. It usually carries retry_suggested and a reason such as source_busy or source_unavailable. |
pending | The block is still pending in a progressive resolve. |
Reason codes
The set is closed: a reason outside it is replaced by error before it
leaves the API. Treat a code you do not recognise as error. The table lists all 140 codes:
| Reason | Kind | Where | Meaning |
|---|---|---|---|
error | row | row, any block | This is a generic failure with no more specific public reason. Retry once; if it repeats, include the X-Request-ID when contacting support. |
not_matched | row | row, resolve 404 | No company was found under the number, name or domain given. |
no_company_key | row | row, resolve 400 | The row carried no usable company_number, company_name or domain. |
ambiguous_match | row | row, resolve 409 | The name or domain matches more than one live company; the candidates are returned and the row is not billed. |
source_unavailable | row | row, officers, controllers, corporate_owners, ultimate_owner, company_identity, registered_address, industry, charges, filing_status | The requested data was unavailable. This says nothing about the company. On a block, retry_suggested is set; a failed row can be resubmitted. |
resolver_error | row | row, resolve 502 | The row could not be resolved because of an error on Zorro's side. Resubmit the row. |
worker_lost | row | row | The row was not resolved and can be resubmitted. |
invalid_company_number | row | row, resolve 400 | The company_number is not a UK company number (8 characters, for example 07706156 or SC123456). GET /v1/resolve answers 400; in a job, only that row goes to the errors page. |
no_company_identified_on_site | row detail | resolve 404 reason, errors page site_lookup | The website names no active company by number or exact legal name. |
site_lookup_not_run | row detail | errors page site_lookup | The live site lookup for an unknown domain runs only on a single resolve or a job of ten rows or fewer, and this row was in a larger job. |
only_related_company_domain | block | domain | The only website on file belongs to a related (owning) company; it is returned under related_company_domain, never as this company's domain. |
inheritance_flag_without_a_related_url | block | domain | The website on file may belong to a related company and cannot be confirmed, so it is returned flagged for review. |
provenance_uncertain_but_validated | block | domain | The website's provenance was uncertain, but domain_validation on the same row accepted it; the flag is cleared. |
not_found_after_search | block | domain | No website was found for the company. |
search_unavailable | block | domain | No website result was available. This says nothing about the company; retry_suggested is set. |
no_domain_found | block | domain, domain_validation, trading_address | No website is on file and no live search was asked for. |
no_corporate_owner_on_the_register | block | related_company_domain, ultimate_owner | The PSC register names no current corporate owner, so there is no owner to follow. |
owner_has_no_website | block | related_company_domain | The register names a corporate owner, but no website is on file for it (or its number is not usable). |
no_psc_data_on_file | block | corporate_owners, controllers, ultimate_owner, related_company_domain | No PSC register is on file for the company. This is not the same as a register that names nobody, which returns an empty list. |
ultimate_owner_not_resolved | block | ultimate_owner | The register names a corporate owner, but the chain above it could not be resolved. |
no_address_on_file | block | registered_address | No registered office address is on file for the company. |
no_charges_data_on_file | block | charges | No charge information is in the company file. |
no_property_titles_on_file | block | property | No HM Land Registry title matched the company number. This does not prove the company owns no property. |
no_officers_on_file | block | officers | No officers are on file for the company. |
no_identity_on_file | block | company_identity | No incorporation date, category or status is on file for the company. |
no_filing_dates_on_file | block | filing_status | No accounts or confirmation statement dates are on file for the company. |
no_registry_dates_on_file | block | registry_events | No register event dates are on file for the company. |
no_headcount_on_file | block | employees_filed | No usable filed employee count is on file. |
no_employee_estimate_on_file | block | employees_live, employees | No third-party headcount estimate is on file. |
not_available_yet | block | any block | The block is in the catalogue but cannot be answered yet. |
owner_not_on_file | block | corporate_owners (owners[].nature_of_business) | The register names an owner with no company file (for example a foreign entity), so there is no description to give. |
domain_name_mismatch | block | related_company_domain | The owner's website shares no distinctive word with the owner's name, so it is returned with that doubt stated. |
not_own_site | block | domain, domain_validation, resolve 404 reason | The domain is a register, directory, social or site-builder host (host_type says which): a page about the company, not its own site. |
validation_not_requested | block | domain_validation | validation was set to none: the domain comes back unscored and flagged. |
site_unreachable | block | domain, domain_validation, trading_address, resolve 404 reason | The site gave no HTTP response at all (no DNS, connection refused or timeout). Not scored and not rejected. |
name_matches_no_address_evidence | block | domain_validation (value.reason) | The company's name is on the site and nothing contradicts it, but no address, postcode, director or phone is shown; the outcome is not lowered below review. |
filed_value_implausible | block | employees_filed, employees | Every filed headcount on file is between 0 and 1 and is marked as implausible; the affected years are listed. |
no_entitlement | block | any block (not_enabled), options (403) | The block or option is not enabled for this account. |
not_for_demo_key | block | contacts (not_enabled) | This block is not available to a demo key (named people and paid lookups). |
site_not_readable | block | domain_validation, trading_address, resolve 404 reason | The site answered, but with no readable text (for example a JavaScript stub). |
site_blocked | block | domain_validation, trading_address, resolve 404 reason | The site answered and refused the request (HTTP 401, 403, 429 or 503). retry_suggested is set. |
no_description_on_file | block | nature_of_business, corporate_owners (owners[].nature_of_business) | No written description of the company is on file. |
no_domain_to_read | block | nature_of_business (live read) | The company has no website for the requested live read. |
site_unreadable | block | nature_of_business (live read) | The live site read could not extract usable text from the site. |
site_read_unavailable | block | nature_of_business (live read) | The website could not be read for this request. This says nothing about the company; retry later. |
no_accounts_filed | block | financials_live | The company has no accounts on file at Companies House. |
accounts_not_machine_readable | block | financials_live | The latest accounts were filed on paper or as PDF, with no machine-readable version. |
register_unavailable | block | financials_live | Companies House did not answer the filing request. Retry. |
accounts_unreadable | block | financials_live | The filing was retrieved but could not be read. |
no_events_in_window | block | signals | There were no register or company events in the last year. |
events_unavailable | block | signals | The event sources could not be read for this request. Retry later. |
accounts_ocr_unavailable | block | financials_live (PDF path) | PDF accounts could not be read for this request. Retry later. |
contacts_disabled | block | contacts | The contacts block is not available. |
contacts_unavailable | block | contacts | The contact lookup could not run for this request. Retry later. |
no_people_found | block | contacts | No decision-maker matching the requested roles was found. |
no_emails_found | block | contacts | People were found, but no business email was found for any of them. |
no_domain_for_email | block | contacts (per contact) | No company website is on file against which to look up an email address. |
no_domain_for_phone | block | contacts (per contact) | No company website is on file against which to look up a phone number. |
no_email_found | block | contacts (per contact) | The lookup ran and found no email for this person. |
no_phone_found | block | contacts (per contact) | The lookup ran and found no phone number for this person. |
non_uk_only | block | contacts (per contact) | Only non-UK phone numbers were found; none is returned. |
shared_number_only | block | contacts (per contact) | The only number found was already given to another contact in the same answer. |
lookup_failed | block | contacts (per contact) | The lookup for this person failed on Zorro's side. Retry later. |
no_planning_applications | block | planning | The planning register holds no applications naming the company. |
no_planning_on_file | block | planning | No planning answer is on file and none was searched for. This does not prove there are no applications. |
planning_register_unavailable | block | planning | The planning register could not be searched. |
no_scores_on_file | block | scores | No scores are on file for the company. |
no_website_to_match | block | funding, news_signals, web profile blocks | The block matches on the company's own website, and none is on file. |
no_funding_on_record | block | funding | The matched record has no funding rounds. |
enrichment_unavailable | block | funding | The organisation data could not be read for this request. Retry later. |
no_validated_website | block | trading_address | The company's website was rejected by domain_validation (now or in the last 30 days), so it is not read for an address. |
no_address_on_site | block | trading_address | The site was read and shows no address. |
insufficient_corroboration | block | trading_address | A candidate address was found but not confirmed by an independent source. |
conflicting_sources | block | trading_address | The sources disagree; every candidate is listed for review. |
postcode_register_unavailable | block | trading_address | The postcode register could not be read. |
google_business_unavailable | block | trading_address | google_business was asked for and the listing check could not run. |
lookups_incomplete | block | contacts | Not every lookup finished; partial is set and the contacts found so far are returned. |
not_completed | block | any block left pending | The pending block was not completed because the row ended after a failure or cancellation. The block's status is error, with retry_suggested set. |
no_sic_on_file | block | industry, financial_benchmarks | The register has no SIC code for the company (including entries that read "None Supplied"). |
no_financials_on_file | block | financials_filed | No accounts history, figures from PDF accounts or estimated revenue are on file. |
no_modelled_revenue | block | revenue_estimate | No estimated revenue is available for the company. |
no_ratios_to_benchmark | block | financial_benchmarks | The company has no ratio from filed figures, or from figures read from PDF accounts, to place among peers. |
cohort_too_small | block | financial_benchmarks | The peer cohort contains fewer than twenty companies. |
benchmark_unavailable | block | financial_benchmarks | The peer comparison could not be computed for this request. Retry later. |
accounts_read_failed | block | financial_benchmarks | financials_live ran in the same request and could not read the accounts, so there is nothing to place among peers yet. Retry later. |
financials_in_live_filing_only | block | financials_filed | No accounts history is on file; the figures are in the filing that financials_live read in the same request. |
bank_income_line_unconfirmed | block | financials_filed (withheld), financials_live (withheld) | A bank's turnover figure is withheld: the printed income line it came from is not confirmed. |
bank_profit_line_unconfirmed | block | financials_filed (withheld), financials_live (withheld) | A bank's operating profit figure is withheld: the printed line it came from is not confirmed. |
holding_company_no_trading_revenue | block | revenue_estimate, financials_filed (modelled) | A holding company, fund or head office is not given an estimated trading revenue. |
share_capital_as_equity | block | financials_filed (withheld) | The equity figure is the share capital line, so it is withheld rather than corrected. |
equity_implausible_against_balance_sheet | block | financials_filed (withheld) | The equity figure cannot be right given the rest of the balance sheet, so it is withheld. |
balance_sheet_breaks_from_adjacent_year | block | financials_filed (flagged) | The balance sheet cannot belong to the same company as the adjacent year's, so it is flagged or withheld, never corrected. |
modelled_implausible_against_filed_turnover | block | financials_filed (withheld) | The filed turnover contradicts the estimated turnover, so the estimate is withheld. |
subsidiary_search_unavailable | block | subsidiaries | No subsidiary result is available. No empty list is returned because this does not show that the company owns nothing. |
no_profile_on_file | block | firmographics | No company profile field is on file. |
no_comparables_found | block | comparables | The peer search found no companies. |
peer_search_unavailable | block | comparables | The peer search could not run. |
few_comparable_peers | block | comparables | Fewer than three comparable companies were found; those companies are returned flagged. |
label_contradicts_filed_sic | block | industry | The industry label sits in a different SIC section from every code the company filed, so it is returned flagged. |
no_business_intelligence_on_file | block | business_intelligence | No website-derived company information is on file. |
site_describes_another_company | block | business_intelligence | The site names another company's number and name; the text is returned as flagged. |
no_summary_on_file | block | company_summary | No summary is available for the company. |
summary_unavailable | block | company_summary | The summary could not be produced for this request. Retry later. |
no_signals_on_file | block | news_signals | No event timeline is on file and no lookup was asked for. |
no_signals_on_record | block | news_signals | A lookup ran and found no events. |
signals_unavailable | block | news_signals | The event lookup could not run for this request. Retry later. |
web_profile_not_enabled | block | web_traffic, web_presence, technologies, employees | Web profile data is not available. |
no_web_profile_found | block | web_traffic, web_presence, technologies, employees | No web profile was found for the company's own site. |
web_profile_unavailable | block | web_traffic, web_presence, technologies, employees | The web profile could not be retrieved for this request. Retry later. |
no_web_traffic_on_profile | block | web_traffic | A profile was found but carries no traffic figures. |
no_technologies_on_file | block | technologies | No detected technologies are on file. |
no_web_presence_on_file | block | web_presence | No web presence facts are on file. |
no_employee_figures_on_file | block | employees | No headcount figure from any source is on file. |
web_profile_outside_uk | block | web_traffic, web_presence, technologies | The answer was built on a profile headquartered outside the UK, so it is returned flagged, with the country in detail. |
no_accounts_reading_on_file | block | employees (filed_ocr) | No figures read from PDF accounts are on file. |
no_headcount_in_accounts_reading | block | employees (filed_ocr) | Figures read from PDF accounts are on file, without a headcount. |
no_workforce_on_profile | block | employees (professional_profiles) | The matched web profile has no workforce figure. |
org_enrichment_not_held | block | employees (org_enrichment) | Organisation data is not on file for the company; this block does not look it up. |
website_not_validated | block | domain, domain_validation, website-derived blocks | The website was rejected, or scored under min_confidence: website-derived data is not served. |
website_needs_review | block | domain_validation, website-derived blocks | The website's outcome is review, at or above min_confidence, so website-derived data is returned with flagged_for_review. |
website_unverified | block | website-derived blocks | There is no verdict on the website (validation was declined, or the check did not finish), so website-derived data is returned with flagged_for_review. |
website_not_verifiable | block | website-derived blocks | The site could not be read for this request, so it could not be checked: website-derived data is withheld. |
accounts_made_up_date_off_reference_date | block | filing_status | The accounts made-up date is not on the company's accounting reference date, so the value is returned flagged, with the expected date beside it. |
bank_no_modelled_turnover | block | revenue_estimate, financials_filed (modelled) | No estimated turnover is returned for a bank or building society (SIC 64110, 64191, 64192). |
none_on_register | block | officers, controllers, charges | The Companies House register was checked and holds none. |
first_accounts_not_yet_due | block | financials_filed, financials_live, revenue_estimate | No accounts are filed and the first accounts are not due yet (detail.first_accounts_due_on). |
accounts_overdue | block | financials_filed, financials_live, revenue_estimate | No accounts are filed and the register says the due date has passed. |
group_site_prefer_uk_domain | block | domain_validation | The site is a group's global one and the company runs a UK site of its own that passes the same check (detail.preferred_domain). The outcome is review, never accept. |
not_confirmed | block | officers, controllers, charges, filing_status, financials_filed | There is no value, and the absence could not be confirmed for this request. This says nothing about the company; retry later. |
source_busy | block | domain, domain_validation, officers, controllers, corporate_owners, ultimate_owner, company_identity, registered_address, industry, filing_status, any live block | The requested data was unavailable for this request. retry_suggested is set. |
not_looked_up | block | contacts (per contact) | When contacts_lookup=cached_only, no email address or phone number is available for this person. |
owner_website_is_this_companys_own | block | related_company_domain | The owner's website on file is this company's own website, so it is not returned as the owner's. |
no_majority_controller | block | ultimate_owner | The register names corporate owners and none holds majority control (more than half of the shares or votes, or the right to appoint the board). The largest holder is in detail.largest_holder. |
dormant_company | block | revenue_estimate, financials_filed (modelled) | The latest accounts filed are dormant accounts, so no estimated trading revenue or profit is returned. |
modelled_profit_implausible | block | financials_filed (withheld) | An estimated profit is withheld if it is larger than the revenue beside it or has no revenue beside it. |
modelled_repeated_across_periods | block | financials_filed (withheld) | An estimated figure repeated unchanged across periods is withheld. |
ratio_implausible | block | financials_filed (withheld), financials_live (withheld) | A ratio whose inputs cannot describe one company's year (a denominator too small, or a value far outside any real range) is withheld. |
accounts_read_no_figures | block | financials_live | The machine-readable filing carried no figure for its latest period. |
sources_disagree | block | employees_filed, employees_live | The two headcount figures on the row differ by a factor of three or more; both are returned as flagged. |
estimate_input_failed_balance_sheet_check | block | financials_filed (withheld) | The estimated figures for that period are withheld because the balance sheet they depend on failed a consistency check. |
Retrying
429: wait for the number of seconds inRetry-After, then retry. Spread bulk work across jobs rather than sending bursts of resolve requests.500,502and network errors: retry with exponential backoff and jitter, at most a few times. Send anX-Idempotency-Keywith job submissions and resolve requests to make retries idempotent. Include theX-Request-IDresponse header when contacting support.- A block with
retry_suggested: true(for examplesource_busy,source_unavailable,site_blocked, ornot_confirmedabsences) could not be completed or confirmed for this request; the same request may succeed later. Resubmit only those rows. - A row with
retryable: truecan be resubmitted as it is.not_matchedandno_company_keywill not change on retry;ambiguous_matchneeds a company number. - For a
4xxresponse other than 429, fix the request before retrying it; otherwise, the answer is the same.
Limits
| Limit | Value |
|---|---|
| Requests (all endpoints) | 600 a minute, per key |
Requests (GET /v1/resolve) | 60 a minute, per key |
| Over the allowance | 429 with Retry-After (seconds) |
| Rows per job | Up to 250,000, or the key's own max_rows_per_job (see GET /v1/whoami); more rows return 413 |
| Results and errors page size | 100 by default, 1,000 max (?limit=) |
| Finding the company behind an unknown domain | A single resolve or a job of ten rows or fewer |
| Results availability | Until the job's expires_at; after it, results and errors answer 410 |
| Usage range | 30 days by default, at most 366 days a request |
Idempotency
Send an X-Idempotency-Key header on POST /v1/enrich/jobs
(or on GET /v1/resolve) to make a retry safe:
- Same key and same body: you get the original job back, with
200instead of202. The original job and its billing are unchanged. On a resolve, the query parameters count as the body. - Same key with a different body: you get
409. Use a new key, or resend the original body exactly.
The idempotency key is scoped to your account, not to the API key that sent it. Reusing an idempotency key on another API key for the same account has the same effect, which matters while you rotate keys.
A replay, and a conflict
HTTP 200
{
"job_id": "d99ba955-f280-4723-b49a-fbb3dd872875",
"status": "queued",
"blocks": [
"domain",
"domain_validation"
],
"counts": {
"total": 3,
"processed": 0,
"succeeded": 0,
"not_found": 0,
"failed": 0,
"site_unreachable": 0
},
"delivery": {
"mode": "pull"
},
"results_ready": false,
"expires_at": null,
"created_at": "2026-09-15T07:07:47.705727Z",
"finished_at": null
}
HTTP 409
{
"detail": "This idempotency key was already used for a different request. Use a new key, or resend the original body."
} About the data
Sources and freshness
Every value says where it came from (source) and, where a date exists,
how current it is (as_of).
as_of
as_of is the date of the information: a filing date, a year for a filed
figure, or the time the value was checked. A value with no date has no as_of.
detail.freshness
Register answers (officers, controllers, ownership, charges, identity, address, industry, filing status, filed financials, property) carry detail.freshness:
| Field | Type | Meaning |
|---|---|---|
checked_live | boolean | true: Companies House was checked for this request. false: the answer was not checked live for this request; as_of gives the date of the information. |
checked_at | string | When the live check was made. Present only when checked_live is true. |
// part of the officers block in the call prep example
{
"status": "available",
"source": "companies_house",
"as_of": "2026-06-01",
"detail": {
"freshness": {
"checked_live": false
}
}
} retry_suggested
retry_suggested: true means the answer could not be completed or confirmed for
this request. The same request may succeed later.
What an absence means
not_foundwithdetail.freshness.checked_live: true(for examplenone_on_register) is confirmed: the register holds none.not_foundwithnot_confirmedcould not be confirmed for this request. It is not a statement that the company has none; retry later.source_busyandsource_unavailablecome withretry_suggested: true.
flagged_for_review
flagged_for_review: true means read reason before using the
value. The value is still returned. Typical causes are a website whose check returned review or could not run, an estimated figure or one read from PDF accounts, a date that is not on the accounting reference date, or a label that contradicts the filed SIC code. Headline financial figures carry a *_basis (filed, ocr_read or modelled), and the response says contains_modelled when part of the value is estimated.
The website verdict
| Verdict | What to do |
|---|---|
accept | Use the website. |
review | The website is unconfirmed; check it before use or leave it out. |
reject | Not this company's website. Website-derived data is withheld. |
| not verifiable | The site could not be read (site_unreachable, site_blocked, site_not_readable). There is no verdict; retry later if it matters. |
Security and data handling
- Send the key from your server in the
Authorizationheader using the Bearer scheme. Do not include a key in browser-based or mobile code. - The secret is shown once, when the key is created, and cannot be shown again. Keep it in a secret store.
- Zorro revokes a key when you ask; a revoked key answers
401. To rotate, ask for a second key, move your traffic to it, then ask us to revoke the first. - Jobs, results, errors and usage belong to the account: a job id from another account answers
404.
For any other information needed for a security review, contact [email protected].
Versioning
The API is versioned in the path: /v1.