Zorro MCP is live. Plug private markets into Claude. Try it

Zorro API

Zorro's REST API returns UK company data. Resolve one company in the same request, or submit a list as a job and retrieve the results. Both return the same company file for each company.

Get started

Overview

Base URL
https://api.getzorro.ai/v1
Format
JSON over HTTPS
Coverage
UK-first: the block catalogue is built on the Companies House register.
Keys
Bearer keys are issued per account with read or enrich scope (request one).
Machine-readable

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.

Try the API

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.

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

ScopeGrants
readThis scope grants access to GET /v1/whoami, GET /v1/blocks, GET /v1/usage, and a job's status, results and errors.
enrichThis 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"
  }
}
FieldMeaning
limits.max_rows_per_jobThe 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_limitsThis field gives the requests per minute for this key, across all endpoints and for GET /v1/resolve on its own.
limits.expires_atThe 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_status and status_group to determine whether the company is trading.
  • filing_status contains the filing dates and any overdue flags.
  • officers.value.current identifies who runs the company. as_of gives the date of the list.
  • controllers and corporate_owners identify who controls the company. An empty list means no controller is recorded.
  • charges.value.holders identifies 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.discover defaults to ["domain"]). Pass "discover": [] to skip that search, or cap it with max_live_lookups; rows past the cap answer source_busy.
  • counts.site_unreachable tells you how many sites would not open; you can submit those rows again.
  • Keep min_confidence at 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 · no key needed

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.

StatusWhenBody
302Redirect to the published description.
429The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After.Error
500The 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

Check a key and see its access · scope read · 600 requests a minute per key

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.

StatusWhenBody
200The key is valid.WhoAmI
401The key is missing, malformed, revoked or wrong. The message is the same in every case.Error
403The key lacks the scope this endpoint needs; the message names it.Error
429The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After.Error
500The 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

List the block catalogue · scope read · 600 requests a minute per key

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.

StatusWhenBody
200The catalogue is returned.BlockCatalog
401The key is missing, malformed, revoked or wrong. The message is the same in every case.Error
403The key lacks the scope this endpoint needs; the message names it.Error
429The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After.Error
500The 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

Resolve one company · scope enrich · 60 requests a minute per key

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.

ParameterInTypeDescription
company_numberquerystringThis is the Companies House number. Give exactly one of company_number, company_name or domain.
company_namequerystringThis is the registered name. More than one live match answers 409 with candidates.
domainquerystringProvide a bare host or URL. A register, directory, social or site-builder host answers 404 not_own_site.
blocksquerystringProvide 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.
validationquerystringThe domain_validation modes are none, rules, live, officers, ai. The default modes are rules,live.
min_confidencequerynumberSet your threshold from 0 to 1. The default is 0.5.
defer_livequery"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.
waitquery"true" | "false"When false, the response is 202 with the job and results_url; collect the row from there.
storequery"true" | "false"When false, the row is not kept for later reads (expires_at null, stored false).
discoverquerystringSpecify the blocks to look up when the company has no value on file. This is off by default on resolve.
max_live_lookupsqueryintegerThis sets the number of website searches allowed and defaults to 1 when discover is given.
refquerystringThis is your reference, which is echoed back.
read_accounts_pdfquery"true" | "false"For financials_live, this reads PDF-only accounts. It is off by default.
web_searchquery"true" | "false"This is a Premium option enabled per account.
google_businessquery"true" | "false"For trading_address, this adds the listing check.
contact_rolesquerystringFor contacts, the values are owner, director, finance, operations, in order of preference.
max_contactsqueryintegerFor contacts, use 1 to 5; the default is 3.
verify_emailsquery"true" | "false"For contacts, the default is true.
contacts_lookupquery"cached_only" | "live"For contacts, use cached_only or live; the default is live.
include_phonequery"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_linkedinquery"true" | "false"For contacts, the default is true.
X-Idempotency-KeyheaderstringThis 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.
StatusWhenBody
200The company is returned. With defer_live, the response may be incomplete.ResolveResponse
202With wait=false, the job is returned for you to poll.Job
400The 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
401The key is missing, malformed, revoked or wrong. The message is the same in every case.Error
403The key lacks the enrich scope, or none of the requested blocks is enabled for it.NotEnabledError | Error
404No company matched.ResolveNotFound
409More than one live company matched, with candidates; or the X-Idempotency-Key was used for a different request.AmbiguousMatch | Error
429The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After.Error
500The request failed on Zorro's side. The body carries request_id when available; the X-Request-ID header always does.Error
502We 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

Submit a list as a job · scope enrich · 600 requests a minute per key

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.

ParameterInTypeDescription
X-Idempotency-KeyheaderstringThis 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).

StatusWhenBody
200A replay with the same X-Idempotency-Key and the same body. You get the original job and nothing new runs.Job
202The job was created.Job
400The request body is invalid.FieldErrors
401The key is missing, malformed, revoked or wrong. The message is the same in every case.Error
403The key lacks the enrich scope, no requested block is enabled, or a premium option is not enabled.NotEnabledError | Error
409The X-Idempotency-Key was used for a different body.Error
413The request contains more rows than the job or key allows. Split the file.FieldErrors
429The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After.Error
500The 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}

Get a job's status · scope read · 600 requests a minute per key

This call returns the job's state and progress counters. While the job has not finished, retry_after_ms says when to poll again.

ParameterInTypeDescription
job_idpathstringThe job id from job creation or from a resolve. An id that is not yours, or not an id, answers 404.
StatusWhenBody
200The job is returned.Job
401The key is missing, malformed, revoked or wrong. The message is the same in every case.Error
403The key lacks the scope this endpoint needs; the message names it.Error
404There is no such job for this account.Error
429The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After.Error
500The 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

Page through a job's rows · scope read · 600 requests a minute per key

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.

ParameterInTypeDescription
job_idpathstringThe job id from job creation or from a resolve. An id that is not yours, or not an id, answers 404.
cursorquerystringUse next_cursor from the previous page. Omit it to start.
limitqueryintegerThe page size defaults to 100 and is at most 1000. A missing or invalid value uses the default.
StatusWhenBody
200A page is returned.ResultsPage
400The cursor is malformed.Error
401The key is missing, malformed, revoked or wrong. The message is the same in every case.Error
403The key lacks the scope this endpoint needs; the message names it.Error
404There is no such job for this account.Error
410The rows are no longer available (after expires_at).Gone
429The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After.Error
500The 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

Page through a job's quarantined rows · scope read · 600 requests a minute per key

This call returns the rows that could not be resolved at all, each with a reason and whether a retry is worthwhile.

ParameterInTypeDescription
job_idpathstringThe job id from job creation or from a resolve. An id that is not yours, or not an id, answers 404.
cursorquerystringUse next_cursor from the previous page. Omit it to start.
limitqueryintegerThe page size defaults to 100 and is at most 1000. A missing or invalid value uses the default.
StatusWhenBody
200A page is returned.ErrorsPage
400The cursor is malformed.Error
401The key is missing, malformed, revoked or wrong. The message is the same in every case.Error
403The key lacks the scope this endpoint needs; the message names it.Error
404There is no such job for this account.Error
410The rows are no longer available (after expires_at).Gone
429The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After.Error
500The 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

Cancel a job · scope enrich · 600 requests a minute per key

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.

ParameterInTypeDescription
job_idpathstringThe job id from job creation or from a resolve. An id that is not yours, or not an id, answers 404.
StatusWhenBody
200The job is returned in its current state.Job
401The key is missing, malformed, revoked or wrong. The message is the same in every case.Error
403The key lacks the scope this endpoint needs; the message names it.Error
404There is no such job for this account.Error
429The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After.Error
500The 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

Usage by block or by day · scope read · 600 requests a minute per key

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.

ParameterInTypeDescription
fromquerystringThis is the first day, in YYYY-MM-DD format and UTC.
toquerystringThis is the last day, inclusive. The default is today.
granularityquery"block" | "day"Use block (default) or day.
job_idquerystringNarrow to one job.
StatusWhenBody
200Usage.Usage
400The request contains an invalid date, a from date later than the to date, a range over 366 days, or an unknown granularity.Error
401The key is missing, malformed, revoked or wrong. The message is the same in every case.Error
403The key lacks the scope this endpoint needs; the message names it.Error
404job_id is not a job on this account.Error
429The key sent more requests than its per-minute allowance. Wait for the number of seconds in Retry-After.Error
500The 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.

BlockTierSourceLatencyDefault
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.

Source: Companies House register, Third-party data · Latency: instant · On by default

value.TypeDescription
incorporated_onstringIncorporation date. YYYY-MM-DD.
company_categorystringThis is the register's category, e.g. Private Limited Company.
country_of_originstringThis is shown as filed.
legal_formstring
company_statusstringThe register's label verbatim: Active, Dissolved, Liquidation, Active - Proposal to Strike off, ...
status_group"active" | "proposal_to_strike_off" | "insolvency" | "dissolved" | "closed" | "other"
dissolved_onstringWhere a dissolution date is on file. YYYY-MM-DD.
trading_namestringFrom a company profile field, not Companies House (which records none).
trading_name_source"third_party_profile"
other_trading_namesstring[]Further distinct trading names from the same profile fields.
trading_name_withheldstringWhy a profile name was not served (it names another organisation).
previous_namesobject[]

Reasons: source_unavailable, no_identity_on_file, source_busy

registered_address

This block returns the registered office as filed, with only the lines the register holds.

Source: Companies House register · Latency: instant · On by default

value.TypeDescription
address_line_1string
address_line_2string
localitystringThis is the post town.
regionstringThis is the county.
postal_codestring
countrystring
po_boxstring
care_ofstring

Reasons: source_unavailable, no_address_on_file, source_busy

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.

Source: Companies House register, Companies House · Latency: instant · On by default

value.TypeDescription
totalinteger | nullThis is the number of officers ever appointed.
activeinteger | nullThis is the number of current officers.
resignedintegerThis is the number of resigned officers.
currentOfficer[]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.
formerOfficer[]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_truncatedbooleanformer[] was cut at 50.
agesobject

Reasons: source_unavailable, no_officers_on_file, none_on_register, not_confirmed, source_busy

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.

Source: Companies House register, Companies House · Latency: instant · On by default

value.TypeDescription
controllersController[]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_ownerbooleanA company or legal entity is among the live entries.
controlobject
ceased_controllersController[]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.

Reasons: source_unavailable, no_psc_data_on_file, none_on_register, not_confirmed, source_busy

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.

Source: Companies House register, Companies House, Company website · Latency: instant · On by default

value.TypeDescription
has_corporate_ownerboolean
ownersobject[]

Reasons: source_unavailable, no_psc_data_on_file, owner_not_on_file, no_description_on_file, source_busy

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.

Source: Companies House register, Derived by Zorro, Company website · Latency: instant · On by default

value.TypeDescription
sicobject[]
labelstringThis is a plain-English industry label.
label_source"sic" | "nace" | "unknown" | "other"
naceobject[]
tagsobjectThese are activity tags from the website; they are withheld when the website is not validated.
tags_source"zorro_from_website"
sic_is_genericbooleanNone of the filed codes names a trade (dormant, holding, n.e.c.).
declared_activitystringThis is the nature_of_business one-liner for comparison when that block is on the same row.

Reasons: source_unavailable, no_sic_on_file, label_contradicts_filed_sic, source_busy

charges

This block returns outstanding charges and their holders exactly as filed, with charge counts. Satisfied charges and past holders are listed separately.

Source: Companies House register, Companies House · Latency: instant · On by default

value.TypeDescription
outstanding_countintegerCounted from the charge records.
satisfied_countinteger
total_countinteger
unclassified_countintegerThis is the number of charges with no classification.
holdersstring[]These are the distinct holders of outstanding charges, shown verbatim.
chargesCharge[]Outstanding charges. Each item: charge_code, status, created_on, registered_on, satisfied_on, persons_entitled, type, particulars, contains, secured_on, amount_secured.
satisfied_chargesCharge[]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_truncatedboolean
past_holdersstring[]These are the holders of satisfied charges.
latest_created_onstring
latest_satisfied_onstring

Reasons: source_unavailable, no_charges_data_on_file, none_on_register, not_confirmed

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.

Source: Companies House register, Companies House · Latency: instant · On by default

value.TypeDescription
accounts_last_made_up_onstringYYYY-MM-DD.
accounts_next_due_onstringYYYY-MM-DD.
accounts_next_made_up_tostringYYYY-MM-DD.
accounts_categorystringThis is the register's accounts category, e.g. FULL, SMALL, MICRO ENTITY, NO ACCOUNTS FILED.
accounts_overduebooleanThe register's overdue flag.
confirmation_statement_last_made_up_onstringYYYY-MM-DD.
confirmation_statement_next_due_onstringYYYY-MM-DD.
confirmation_statement_overduebooleanThe register's overdue flag.
accounts_last_made_up_on_expectedobjectThis 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.
correctionsobjectThis is a corrected due date because accounts on the register had already met the earlier date.

Reasons: source_unavailable, no_filing_dates_on_file, accounts_made_up_date_off_reference_date, not_confirmed, source_busy

domain

This block returns the registrable domain of the company's own website, never a related company's site.

Source: Company website · Latency: instant · On by default

value is a string.

Reasons: only_related_company_domain, inheritance_flag_without_a_related_url, provenance_uncertain_but_validated, not_found_after_search, search_unavailable, no_domain_found, not_own_site, site_unreachable, website_not_validated, source_busy

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.

Source: Company website, Companies House register · Latency: live · On by default

value.TypeDescription
domainstringThis is the website checked or, after a passing redirect, the final domain.
dns_resolvesboolean | nullIn live mode, the name resolves. null means that the check could not be completed.
email_deliverableboolean | nullIn live mode, the domain accepts mail. null means that the check could not be completed.
site_liveboolean | nullIn live mode, the site answered over HTTP(S).
http_statusinteger | nullThis is the HTTP status returned for the website.
final_urlstringThis is the final URL after redirects.
final_domainstringThe domain that the domain on file forwards to.
notestring
director_check"companies_house" | "source_busy"This identifies the source of the director check.
scoreintegerThis 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.
reasonstringname_matches_no_address_evidence when only the name tied the site to the company.
checksobjectThis 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.
contributionsobjectThis object contains the contribution associated with each check.
reasonsstring[]These are short notes about the checks.
ai_outcome"pass" | "fail"ai mode: reported beside the score; it does not change the score.
ai_reasonstring

Reasons: no_domain_found, not_own_site, validation_not_requested, site_unreachable, name_matches_no_address_evidence, site_not_readable, site_blocked, website_not_validated, website_needs_review, group_site_prefer_uk_domain, source_busy

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.

Source: Companies House register, Derived by Zorro · Latency: instant · On by default

value.TypeDescription
company_namestring | null
company_numberstring | null
depthintegerThis is the number of steps from this company to the top.
resolved_bystringThis is the resolution category for the top of the chain.
chainobject[]

Reasons: source_unavailable, no_corporate_owner_on_the_register, no_psc_data_on_file, ultimate_owner_not_resolved, source_busy, no_majority_controller

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.

Source: Filed accounts, Modelled by Zorro, PDF accounts · Latency: instant · On by default

value.TypeDescription
revenue_gbpnumberThis is the newest filed or ocr_read turnover, or the modelled turnover only when nothing was filed. Check revenue_basis.
revenue_basisstringThe value is filed, ocr_read or modelled.
revenue_period_endstring
profit_loss_gbpnumberThis is the headline profit and follows the same rule.
profit_loss_basisstring
profit_loss_period_endstring
turnover_change_pctnumber
turnover_change_basisstring
turnover_change_period_endstring
best_turnover_growth_pctnumber
best_turnover_growth_basisstring
profit_margin_stableboolean
filed_revenueobject
filed_profit_lossobject
modelled_profit_lossobject
modelled_revenueobjectThis is the modelled revenue with its band.
modelled_revenue_withheldReasonCodeThis 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_definitionobject
withheldobject[]These are withheld figures, each with period_end, side, field and reason.
yearsintegerThis is the number of periods.
periodsFinancialPeriod[]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.

Reasons: no_financials_on_file, financials_in_live_filing_only, bank_income_line_unconfirmed, bank_profit_line_unconfirmed, holding_company_no_trading_revenue, share_capital_as_equity, equity_implausible_against_balance_sheet, balance_sheet_breaks_from_adjacent_year, modelled_implausible_against_filed_turnover, bank_no_modelled_turnover, first_accounts_not_yet_due, accounts_overdue, not_confirmed, dormant_company, modelled_profit_implausible, modelled_repeated_across_periods, ratio_implausible, estimate_input_failed_balance_sheet_check

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.

Source: Filed accounts, PDF accounts · Latency: instant · On by default

value is an integer.

Reasons: no_headcount_on_file, filed_value_implausible, sources_disagree

nature_of_business

This block describes what the company does, based on its website. derived_from says which description was used.

Source: Company website, Company website · Latency: instant · On by default

value.TypeDescription
one_linerstringThis is a short description.
product_servicestringThis describes the offering in detail.

Reasons: no_description_on_file, no_domain_to_read, site_unreadable, site_read_unavailable

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.

Source: Companies House register, Derived by Zorro · Latency: instant · Switched on per account

value.TypeDescription
subsidiariesobject[]
countinteger
active_countinteger
truncatedbooleanThis is true when there are more than 100 matching companies.

Reasons: subsidiary_search_unavailable

property

This block lists the freehold and leasehold titles matched on company number, with postcodes, addresses and the total price paid (not a valuation).

Source: HM Land Registry · Latency: instant · On by default

value.TypeDescription
title_countinteger | null
total_price_paid_gbpnumberThe sum paid for the titles when bought. Not a valuation.
tenurestring[]
postcodesstring[]
addressesstring[]

Reasons: no_property_titles_on_file

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:

KeyTypeMeaning
statusBlockStatusavailable: 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.
valueanyThis is present only when status is available. Its shape depends on the block; see the block schemas.
sourcestringWhere 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_ofstringThe 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.
confidencenumberA 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_reviewbooleanWhen true, read reason before relying on the value. The value is still returned.
reasonReasonCodeThis 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.
detailEnvelopeDetailHow the answer was built. Blocks can add their own keys.
partialobjectFor not_found or error, this contains available information, such as the domain and whether the site answered.
retry_suggestedbooleanThis indicates that the block was not completed or confirmed for the request.
derivedbooleanThe 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_modelledbooleanfinancials_filed: part of the value is modelled (under modelled, or with a *_basis of modelled).
contains_ocr_readbooleanFor financials_filed, part of the value was read from PDF accounts.
contains_derivedbooleanFor financials_filed, ratios computed from filed figures or figures read from PDF accounts are present.
host_type"register" | "directory" | "social" | "site_builder"
verification_profilestringFor domain, this contains website verification information.
final_urlstringdomain: the address actually read after redirects, when a block on the row opened the site.
affected_yearsstring[]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

CallWhat you getUse it for
GET /v1/resolveThe response contains the whole row after every block has returned a result.Use this for blocks returned in the first response.
…&defer_live=trueThe 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=falseThe 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.

StatusMeaningTerminal
queuedAccepted and in progress. counts.processed increases as rows finish.No
completedEvery row has settled and none has failed (rows may be not_found).Yes
partially_completedEvery row has settled, and at least one has failed. The failed rows are on the errors page.Yes
failedThe job could not finish; failure_reason says why.Yes
cancellingA cancel is in progress.No
cancelledRows already in progress are finished and delivered; rows not yet started are skipped and not billed.Yes
runningThis status is reserved.No
Job fieldTypeMeaning
job_idstring
statusJobStatusA 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.
blocksBlockKey[]A block in the catalogue. GET /v1/blocks lists all 37 and shows which ones your key may request.
countsJobCounts
deliveryobject
results_readybooleanThe job is terminal: every row is final.
expires_atstring | nullWhen the job's rows stop being available. It is null while the job is running.
created_atstring
finished_atstring | null
warningsWarning[]Some requested blocks are not enabled for the key; they come back not_enabled and every other block is unaffected.
retry_after_msintegerFor a job that is not terminal, this is the polling delay in milliseconds.
results_urlstringThis 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:

OutcomeMeaning
acceptThe site carries this company's registered identity, with at least one hard match and no objection.
reviewThere is some evidence, but not enough to confirm the site. Treat it as unconfirmed.
rejectThe 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:

OutcomeAgainst min_confidencereasonWebsite-derived blocks
acceptat or abovenone, flagged_for_review: falseserved
reviewat or abovewebsite_needs_reviewserved, flagged
accept or reviewbelowwebsite_not_validatedwithheld
rejectanywebsite_not_validatedwithheld
no scoreNot applicablewebsite_unverified or website_not_verifiableflagged, 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_suggested is 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:

ModeWhat it adds
rulesThis mode compares the site with the company file and returns checks, contributions, score and outcome.
liveThis 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).
officersThis mode compares director names with the Companies House officer list. director_check identifies the source of the director check.
aiThis 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.
noneRuns 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.

StatusWhenBody
200The request succeeded, including a replayed job submission (same X-Idempotency-Key and same body).The resource
202A job was created (POST /v1/enrich/jobs, or GET /v1/resolve with wait=false).Job
example
400The 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 FieldErrors
example
401The key is missing, malformed, revoked or wrong. The message is the same for every case.Error
example
403The 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 Error
example
404GET /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 Error
example
409GET /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 Error
example
410The job's results or errors were requested after its expires_at, and the rows are no longer available. The job itself still answers.Gone
413The job has more rows than allowed (250,000, or the key's own max_rows_per_job).FieldErrors under rows
429The key sent more requests than its per-minute allowance.Error, with a Retry-After header
example
500An error on Zorro's side. The body is always JSON and carries request_id when available.Error
502GET /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

statusMeaning
availableThe value is present. Read flagged_for_review and reason when set.
not_foundThere is no value for this company; reason identifies the type of absence.
not_enabledThe key may not run this block.
errorThe block could not be answered for this request. It usually carries retry_suggested and a reason such as source_busy or source_unavailable.
pendingThe 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:

ReasonKindWhereMeaning
errorrowrow, any blockThis is a generic failure with no more specific public reason. Retry once; if it repeats, include the X-Request-ID when contacting support.
not_matchedrowrow, resolve 404No company was found under the number, name or domain given.
no_company_keyrowrow, resolve 400The row carried no usable company_number, company_name or domain.
ambiguous_matchrowrow, resolve 409The name or domain matches more than one live company; the candidates are returned and the row is not billed.
source_unavailablerowrow, officers, controllers, corporate_owners, ultimate_owner, company_identity, registered_address, industry, charges, filing_statusThe requested data was unavailable. This says nothing about the company. On a block, retry_suggested is set; a failed row can be resubmitted.
resolver_errorrowrow, resolve 502The row could not be resolved because of an error on Zorro's side. Resubmit the row.
worker_lostrowrowThe row was not resolved and can be resubmitted.
invalid_company_numberrowrow, resolve 400The 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_siterow detailresolve 404 reason, errors page site_lookupThe website names no active company by number or exact legal name.
site_lookup_not_runrow detailerrors page site_lookupThe 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.
provenance_uncertain_but_validatedblockdomainThe website's provenance was uncertain, but domain_validation on the same row accepted it; the flag is cleared.
search_unavailableblockdomainNo website result was available. This says nothing about the company; retry_suggested is set.
no_domain_foundblockdomain, domain_validation, trading_addressNo website is on file and no live search was asked for.
no_corporate_owner_on_the_registerblockrelated_company_domain, ultimate_ownerThe PSC register names no current corporate owner, so there is no owner to follow.
owner_has_no_websiteblockrelated_company_domainThe register names a corporate owner, but no website is on file for it (or its number is not usable).
no_psc_data_on_fileblockcorporate_owners, controllers, ultimate_owner, related_company_domainNo 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_resolvedblockultimate_ownerThe register names a corporate owner, but the chain above it could not be resolved.
no_address_on_fileblockregistered_addressNo registered office address is on file for the company.
no_charges_data_on_fileblockchargesNo charge information is in the company file.
no_property_titles_on_fileblockpropertyNo HM Land Registry title matched the company number. This does not prove the company owns no property.
no_officers_on_fileblockofficersNo officers are on file for the company.
no_identity_on_fileblockcompany_identityNo incorporation date, category or status is on file for the company.
no_filing_dates_on_fileblockfiling_statusNo accounts or confirmation statement dates are on file for the company.
no_registry_dates_on_fileblockregistry_eventsNo register event dates are on file for the company.
no_headcount_on_fileblockemployees_filedNo usable filed employee count is on file.
no_employee_estimate_on_fileblockemployees_live, employeesNo third-party headcount estimate is on file.
not_available_yetblockany blockThe block is in the catalogue but cannot be answered yet.
owner_not_on_fileblockcorporate_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_mismatchblockrelated_company_domainThe owner's website shares no distinctive word with the owner's name, so it is returned with that doubt stated.
not_own_siteblockdomain, domain_validation, resolve 404 reasonThe 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_requestedblockdomain_validationvalidation was set to none: the domain comes back unscored and flagged.
site_unreachableblockdomain, domain_validation, trading_address, resolve 404 reasonThe site gave no HTTP response at all (no DNS, connection refused or timeout). Not scored and not rejected.
name_matches_no_address_evidenceblockdomain_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_implausibleblockemployees_filed, employeesEvery filed headcount on file is between 0 and 1 and is marked as implausible; the affected years are listed.
no_entitlementblockany block (not_enabled), options (403)The block or option is not enabled for this account.
not_for_demo_keyblockcontacts (not_enabled)This block is not available to a demo key (named people and paid lookups).
site_not_readableblockdomain_validation, trading_address, resolve 404 reasonThe site answered, but with no readable text (for example a JavaScript stub).
site_blockedblockdomain_validation, trading_address, resolve 404 reasonThe site answered and refused the request (HTTP 401, 403, 429 or 503). retry_suggested is set.
no_description_on_fileblocknature_of_business, corporate_owners (owners[].nature_of_business)No written description of the company is on file.
no_domain_to_readblocknature_of_business (live read)The company has no website for the requested live read.
site_unreadableblocknature_of_business (live read)The live site read could not extract usable text from the site.
site_read_unavailableblocknature_of_business (live read)The website could not be read for this request. This says nothing about the company; retry later.
no_accounts_filedblockfinancials_liveThe company has no accounts on file at Companies House.
accounts_not_machine_readableblockfinancials_liveThe latest accounts were filed on paper or as PDF, with no machine-readable version.
register_unavailableblockfinancials_liveCompanies House did not answer the filing request. Retry.
accounts_unreadableblockfinancials_liveThe filing was retrieved but could not be read.
no_events_in_windowblocksignalsThere were no register or company events in the last year.
events_unavailableblocksignalsThe event sources could not be read for this request. Retry later.
accounts_ocr_unavailableblockfinancials_live (PDF path)PDF accounts could not be read for this request. Retry later.
contacts_disabledblockcontactsThe contacts block is not available.
contacts_unavailableblockcontactsThe contact lookup could not run for this request. Retry later.
no_people_foundblockcontactsNo decision-maker matching the requested roles was found.
no_emails_foundblockcontactsPeople were found, but no business email was found for any of them.
no_domain_for_emailblockcontacts (per contact)No company website is on file against which to look up an email address.
no_domain_for_phoneblockcontacts (per contact)No company website is on file against which to look up a phone number.
no_email_foundblockcontacts (per contact)The lookup ran and found no email for this person.
no_phone_foundblockcontacts (per contact)The lookup ran and found no phone number for this person.
non_uk_onlyblockcontacts (per contact)Only non-UK phone numbers were found; none is returned.
shared_number_onlyblockcontacts (per contact)The only number found was already given to another contact in the same answer.
lookup_failedblockcontacts (per contact)The lookup for this person failed on Zorro's side. Retry later.
no_planning_applicationsblockplanningThe planning register holds no applications naming the company.
no_planning_on_fileblockplanningNo planning answer is on file and none was searched for. This does not prove there are no applications.
planning_register_unavailableblockplanningThe planning register could not be searched.
no_scores_on_fileblockscoresNo scores are on file for the company.
no_website_to_matchblockfunding, news_signals, web profile blocksThe block matches on the company's own website, and none is on file.
no_funding_on_recordblockfundingThe matched record has no funding rounds.
enrichment_unavailableblockfundingThe organisation data could not be read for this request. Retry later.
no_validated_websiteblocktrading_addressThe 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_siteblocktrading_addressThe site was read and shows no address.
insufficient_corroborationblocktrading_addressA candidate address was found but not confirmed by an independent source.
conflicting_sourcesblocktrading_addressThe sources disagree; every candidate is listed for review.
postcode_register_unavailableblocktrading_addressThe postcode register could not be read.
google_business_unavailableblocktrading_addressgoogle_business was asked for and the listing check could not run.
lookups_incompleteblockcontactsNot every lookup finished; partial is set and the contacts found so far are returned.
not_completedblockany block left pendingThe 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_fileblockindustry, financial_benchmarksThe register has no SIC code for the company (including entries that read "None Supplied").
no_financials_on_fileblockfinancials_filedNo accounts history, figures from PDF accounts or estimated revenue are on file.
no_modelled_revenueblockrevenue_estimateNo estimated revenue is available for the company.
no_ratios_to_benchmarkblockfinancial_benchmarksThe company has no ratio from filed figures, or from figures read from PDF accounts, to place among peers.
cohort_too_smallblockfinancial_benchmarksThe peer cohort contains fewer than twenty companies.
benchmark_unavailableblockfinancial_benchmarksThe peer comparison could not be computed for this request. Retry later.
accounts_read_failedblockfinancial_benchmarksfinancials_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_onlyblockfinancials_filedNo accounts history is on file; the figures are in the filing that financials_live read in the same request.
bank_income_line_unconfirmedblockfinancials_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_unconfirmedblockfinancials_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_revenueblockrevenue_estimate, financials_filed (modelled)A holding company, fund or head office is not given an estimated trading revenue.
share_capital_as_equityblockfinancials_filed (withheld)The equity figure is the share capital line, so it is withheld rather than corrected.
equity_implausible_against_balance_sheetblockfinancials_filed (withheld)The equity figure cannot be right given the rest of the balance sheet, so it is withheld.
balance_sheet_breaks_from_adjacent_yearblockfinancials_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_turnoverblockfinancials_filed (withheld)The filed turnover contradicts the estimated turnover, so the estimate is withheld.
subsidiary_search_unavailableblocksubsidiariesNo subsidiary result is available. No empty list is returned because this does not show that the company owns nothing.
no_profile_on_fileblockfirmographicsNo company profile field is on file.
no_comparables_foundblockcomparablesThe peer search found no companies.
peer_search_unavailableblockcomparablesThe peer search could not run.
few_comparable_peersblockcomparablesFewer than three comparable companies were found; those companies are returned flagged.
label_contradicts_filed_sicblockindustryThe industry label sits in a different SIC section from every code the company filed, so it is returned flagged.
no_business_intelligence_on_fileblockbusiness_intelligenceNo website-derived company information is on file.
site_describes_another_companyblockbusiness_intelligenceThe site names another company's number and name; the text is returned as flagged.
no_summary_on_fileblockcompany_summaryNo summary is available for the company.
summary_unavailableblockcompany_summaryThe summary could not be produced for this request. Retry later.
no_signals_on_fileblocknews_signalsNo event timeline is on file and no lookup was asked for.
no_signals_on_recordblocknews_signalsA lookup ran and found no events.
signals_unavailableblocknews_signalsThe event lookup could not run for this request. Retry later.
web_profile_not_enabledblockweb_traffic, web_presence, technologies, employeesWeb profile data is not available.
no_web_profile_foundblockweb_traffic, web_presence, technologies, employeesNo web profile was found for the company's own site.
web_profile_unavailableblockweb_traffic, web_presence, technologies, employeesThe web profile could not be retrieved for this request. Retry later.
no_web_traffic_on_profileblockweb_trafficA profile was found but carries no traffic figures.
no_technologies_on_fileblocktechnologiesNo detected technologies are on file.
no_web_presence_on_fileblockweb_presenceNo web presence facts are on file.
no_employee_figures_on_fileblockemployeesNo headcount figure from any source is on file.
web_profile_outside_ukblockweb_traffic, web_presence, technologiesThe answer was built on a profile headquartered outside the UK, so it is returned flagged, with the country in detail.
no_accounts_reading_on_fileblockemployees (filed_ocr)No figures read from PDF accounts are on file.
no_headcount_in_accounts_readingblockemployees (filed_ocr)Figures read from PDF accounts are on file, without a headcount.
no_workforce_on_profileblockemployees (professional_profiles)The matched web profile has no workforce figure.
org_enrichment_not_heldblockemployees (org_enrichment)Organisation data is not on file for the company; this block does not look it up.
website_not_validatedblockdomain, domain_validation, website-derived blocksThe website was rejected, or scored under min_confidence: website-derived data is not served.
website_needs_reviewblockdomain_validation, website-derived blocksThe website's outcome is review, at or above min_confidence, so website-derived data is returned with flagged_for_review.
website_unverifiedblockwebsite-derived blocksThere 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_verifiableblockwebsite-derived blocksThe 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_dateblockfiling_statusThe 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_turnoverblockrevenue_estimate, financials_filed (modelled)No estimated turnover is returned for a bank or building society (SIC 64110, 64191, 64192).
none_on_registerblockofficers, controllers, chargesThe Companies House register was checked and holds none.
first_accounts_not_yet_dueblockfinancials_filed, financials_live, revenue_estimateNo accounts are filed and the first accounts are not due yet (detail.first_accounts_due_on).
accounts_overdueblockfinancials_filed, financials_live, revenue_estimateNo accounts are filed and the register says the due date has passed.
group_site_prefer_uk_domainblockdomain_validationThe 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_confirmedblockofficers, controllers, charges, filing_status, financials_filedThere is no value, and the absence could not be confirmed for this request. This says nothing about the company; retry later.
source_busyblockdomain, domain_validation, officers, controllers, corporate_owners, ultimate_owner, company_identity, registered_address, industry, filing_status, any live blockThe requested data was unavailable for this request. retry_suggested is set.
not_looked_upblockcontacts (per contact)When contacts_lookup=cached_only, no email address or phone number is available for this person.
owner_website_is_this_companys_ownblockrelated_company_domainThe owner's website on file is this company's own website, so it is not returned as the owner's.
no_majority_controllerblockultimate_ownerThe 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_companyblockrevenue_estimate, financials_filed (modelled)The latest accounts filed are dormant accounts, so no estimated trading revenue or profit is returned.
modelled_profit_implausibleblockfinancials_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_periodsblockfinancials_filed (withheld)An estimated figure repeated unchanged across periods is withheld.
ratio_implausibleblockfinancials_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_figuresblockfinancials_liveThe machine-readable filing carried no figure for its latest period.
sources_disagreeblockemployees_filed, employees_liveThe two headcount figures on the row differ by a factor of three or more; both are returned as flagged.
estimate_input_failed_balance_sheet_checkblockfinancials_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 in Retry-After, then retry. Spread bulk work across jobs rather than sending bursts of resolve requests.
  • 500, 502 and network errors: retry with exponential backoff and jitter, at most a few times. Send an X-Idempotency-Key with job submissions and resolve requests to make retries idempotent. Include the X-Request-ID response header when contacting support.
  • A block with retry_suggested: true (for example source_busy, source_unavailable, site_blocked, or not_confirmed absences) could not be completed or confirmed for this request; the same request may succeed later. Resubmit only those rows.
  • A row with retryable: true can be resubmitted as it is. not_matched and no_company_key will not change on retry; ambiguous_match needs a company number.
  • For a 4xx response other than 429, fix the request before retrying it; otherwise, the answer is the same.

Limits

LimitValue
Requests (all endpoints)600 a minute, per key
Requests (GET /v1/resolve)60 a minute, per key
Over the allowance429 with Retry-After (seconds)
Rows per jobUp to 250,000, or the key's own max_rows_per_job (see GET /v1/whoami); more rows return 413
Results and errors page size100 by default, 1,000 max (?limit=)
Finding the company behind an unknown domainA single resolve or a job of ten rows or fewer
Results availabilityUntil the job's expires_at; after it, results and errors answer 410
Usage range30 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 200 instead of 202. 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:

FieldTypeMeaning
checked_livebooleantrue: 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_atstringWhen 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_found with detail.freshness.checked_live: true (for example none_on_register) is confirmed: the register holds none.
  • not_found with not_confirmed could not be confirmed for this request. It is not a statement that the company has none; retry later.
  • source_busy and source_unavailable come with retry_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

VerdictWhat to do
acceptUse the website.
reviewThe website is unconfirmed; check it before use or leave it out.
rejectNot this company's website. Website-derived data is withheld.
not verifiableThe 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 Authorization header 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.

Help

Support

Support
Questions and issues
Email us at the address below.
support@getzorro.ai
Book a demo Available this week