# Business verification

Read a business identifier against the registers this server holds: a Legal Entity Identifier against the GLEIF Level 1 golden copy, or a UK company number against the Companies House free company data product. Both are bulk files synced locally, so no third party sits in the request path, nothing about a lookup leaves the server, and an answer takes milliseconds.

Where a LEI names its entry in a national register Spaw also holds, the two records are compared and both sides are quoted, so a disagreement is something you can read rather than take on trust.

```
curl https://spaw.co/api/v1/entity \
    -H "Authorization: Bearer sk_live_…" \
    -H "Content-Type: application/json" \
    -d '{"identifier": "213800QILIUD4ROSUO03", "type": "lei"}'
```

## Sending an identifier

`identifier` and `type` are required. `type` is `lei` or `company_number`.

A company number needs the `country` whose register it belongs to, because company numbers are only unique inside their own register; `GB` is the register installed today. A LEI needs no country — the identifier is global by construction. A UK company number is zero-padded to eight characters before it is looked up, so `1234567` and `01234567` are the same company.

An identifier the server holds no register for answers `unsupported_register` rather than guessing, and it is free.

## What comes back

| field | type | meaning |
| --- | --- | --- |
| identifier | `string` | The identifier as the register writes it: a LEI uppercased, a company number padded to eight. |
| identifier_type | `string` | `lei` or `company_number`. |
| found | `boolean` | Whether a register carried it. `false` is free. |
| reason | `string \| null` | Why not: `unknown_identifier`, `malformed_identifier`, `unsupported_register` or `register_unavailable`. |
| name | `string \| null` | The legal name the register carries. |
| status | `string \| null` | `active`, `inactive`, `dissolved` or `unknown`. |
| status_detail | `string \| null` | The register's own wording, which distinguishes a strike-off from a liquidation. |
| country / jurisdiction | `string \| null` | The country of registration, and the jurisdiction of formation, which can be a subdivision (`US-DE`). |
| category / legal_form | `string \| null` | The register's entity category, and the ISO 20275 legal form code where one is carried. |
| registered_at | `date \| null` | When the entity was formed or incorporated. |
| record_updated_at | `date \| null` | When the register last updated this record. |
| checksum_valid | `boolean \| null` | LEI only: whether the check digit is right. Null for identifiers that carry none. |
| registration_status | `string \| null` | LEI only: `ISSUED`, `LAPSED`, `RETIRED`, `ANNULLED`, `MERGED` or `DUPLICATE`. |
| next_renewal_at | `date \| null` | LEI only: when the registration was last due for renewal. |
| other_register / other_identifier | `string \| null` | The national register this record names, and its identifier there. |
| flags | `string[]` | What the registers disagree about, or what one of them warns. |
| agreements | `object[]` | Each cross-register comparison, with both sides quoted. |
| sources | `object[]` | Every register behind the answer, with the date of the copy. |

## The check digit is a typing check

A LEI carries a two-digit check digit computed with ISO 7064 MOD 97-10, the same arithmetic an IBAN uses. `checksum_valid` reports it, and it catches a mistyped or transposed character — nothing more. A perfectly formed LEI can never have been issued, which is what the register lookup answers.

Measured against the published register on 8 September 2026: of 45,515 consecutive real LEIs, 45,512 satisfy the checksum, and the three that do not are all `ANNULLED` registrations — LEIs issued in error and withdrawn. So a record can exist with a failing checksum, which is why the two facts are reported side by side rather than one standing in for the other.

## An entity can outlive its LEI

`status` is about the entity: is this company still a company. `registration_status` is about the identifier: has anyone renewed it. A LEI must be reconfirmed each year against the entity's own national register, and a great many are not, so `LAPSED` is common and means the reference data has not been rechecked recently — not that the company has gone.

When the registration is not current, `lei_registration_not_current` is flagged. If a counterparty needs a current LEI for a regulatory reason, that flag is the one that says they do not have one.

## Comparing two registers

When a LEI record names a Companies House number and both registers are installed, the two are compared:

- **name** — on their letters and digits, with the company-form words (limited, ltd, plc, llp, inc) removed, so `ACME HOLDINGS LTD.` and `Acme Holdings Limited` agree. A real difference raises `name_differs_across_registers`, and both names are in `agreements`.
- **status** — a disagreement raises `status_differs_across_registers`. The national register is the authority on whether a company exists; the LEI record reflects what was last reported. A disagreement usually means the LEI record is stale.
- Where the national register records the company as dissolved, `dissolved_in_national_register` is flagged. That is the strongest signal the product carries: a dissolved company no longer exists as a legal person and cannot enter a contract.

Where there is no counterpart record, `agreements` is empty. An absent register is not a disagreement.

## No addresses, on purpose

Nothing in the response is a registered address, and nothing will be.

A registered address is very often somebody's home — that is why Companies House operates a protected-address regime and why several registers mask an entrepreneur's address by law. An identifier on its own does not say whether a limited company or a sole trader stands behind it, and a sole trader's registered details are personal data in a way a company's are not.

So the product carries country and jurisdiction, which are enough to answer "is this a real, live entity", and holds no register of where people live. If you need to check an address, that is what the address product does, and it is about places rather than people for the same reason.

## Where the data comes from

The Legal Entity Identifier reference data is published by the Global Legal Entity Identifier Foundation under a CC0 1.0 public-domain dedication, as a golden copy refreshed daily. Spaw syncs that file and answers from its own copy. Spaw is not affiliated with, endorsed by or connected to that foundation in any way, and an answer here is Spaw's, not theirs.

UK company data is the Companies House free company data product, used under the Open Government Licence v3.0. It is republished monthly, within five working days of the previous month end, so an answer can be up to five weeks behind the live register. Every answer carries the date of the copy in `sources`, and `/status` lists both registers with the date each was last synced. Where the age of a record matters, read it there.

## Monitored lists

Save up to 500 identifiers and have them re-checked weekly or monthly. There is no daily cadence: the UK company file is republished monthly, so a daily run would bill for data that cannot have changed.

`POST /api/v1/entity/monitors` takes a `name`, a list of `identifiers` and a `cadence`. Each entry is an object of `identifier`, `type` and, for a company number, `country`; a bare string is read as a LEI, because that is the only identifier that needs no country.

The first run happens immediately and only sets the baseline — so saving a list never emails you about what was already true when you saved it. From the next run on, an email goes out for an entry that was fine last time and is not any more:

| status | what happened |
| --- | --- |
| `left_register` | A register carried the identifier before and does not now. For the UK this is what a dissolution looks like — see below. |
| `dissolved` | The register records the company as dissolved. Rare on the UK register, for the reason below. |
| `inactive` | The register no longer records the entity as active. |
| `registration_lapsed` | The LEI registration is no longer current. |
| `registers_disagree` | The two registers have stopped agreeing about the name or the status. |
| `unverifiable` | No register on this server could answer for it. Never alerted on: that is a limit of the data here, not a fact about the company. |
| `ok` | Nothing to report. |

Each run bills each identifier exactly as a single lookup, and the seven-day repeat marker applies — so a weekly cadence over a monthly register costs almost nothing after the first run. A run that exhausts the balance stops where it got to and reports `stopped_reason`.

### How a UK dissolution actually shows up

The Companies House free company data product is a snapshot of the **live** register. A company that is struck off is **removed from the file** rather than marked dissolved — so the statuses that file carries are `Active`, `Active - Proposal to Strike off`, `Liquidation`, `In Administration` and similar, and never `Dissolved`.

That means a dissolution reaches you as the identifier disappearing between runs, which the monitor reports as `left_register` and alerts on. Without that status it would read as `unverifiable` and pass silently, which would be the single worst thing this product could get wrong.

`Active - Proposal to Strike off` is the earlier warning, and it is the most common non-plain status on the register by a wide margin. It folds to `status` `active` — because it is still on the register — with the register's own wording in `status_detail`, so read that field if you want to act before the company goes.

## How long a monitor is kept

A monitor can post its runs as well as email about them. Set `webhook_url` (https) when you create it, or later with a `PATCH`, and the signing secret comes back once in that response and is never readable again. `webhook_events` decides which runs are posted: `changes`, the default, posts only when the run found something new — an identifier that is newly flagged, or one whose status changed — or stopped early, and `every_run` posts after every run, which also tells you the monitor is still running. The body is `{ "event": "entity_monitor.run", "monitor": {…}, "summary": {…}, "changes": [ … ] }`, where `changes` names the identifiers the alert email names, and the delivery is signed with `X-Spaw-Signature-V2`: `t={unix seconds},v2={hex}`, where the hex is the HMAC-SHA256 of `v2:{t}:{raw body}` keyed with the secret. Refuse anything whose `t` is more than five minutes from your own clock — that window is what stops a captured delivery being replayed at you later. The original `X-Spaw-Signature`, the HMAC of the body alone with no timestamp, is sent beside it until 1 March 2027 and still verifies; both recipes are at https://spaw.co/integrations/webhooks. Connection errors and 5xx answers are retried twice, three attempts in all; `webhook_status` and `webhook_detail` on the monitor say how the last delivery went. A URL that resolves onto a private network is refused at delivery, every time, because DNS can be repointed after a URL is saved.

A monitor is changed rather than replaced: `PATCH /api/v1/entity/monitors/{id}` (https://spaw.co/docs/api/update-entity-monitor) takes `name`, `identifiers`, `cadence` and the webhook fields, and applies only the ones you send. That matters because what the last run recorded per entry is the baseline every alert is compared against: deleting a monitor and creating it again throws that away, and the first run of the new one only sets a baseline — a full cycle of silence, which is the one thing a monitor is bought to prevent. Replacing the list keeps what was recorded about the identifiers that stayed, deletes what was recorded about the ones you removed, and gives a new entry no baseline. Changing the cadence neither skips a run nor starts a second one: a run already scheduled sooner than the new interval still happens, and a longer interval applies from the one after it. A body naming no field it could change answers `422`.

A monitor that stops being run is deleted, and the identifiers and results it stored go with it. `prunes_after_days` on every monitor payload says how long that takes.

A monitor in active use is never pruned: each run advances `last_run_at`, so the window only closes on one that has genuinely stopped — the account ran out of credits and never came back, or the list was created and never ran. That is the point at which holding somebody else's identifiers stops serving the purpose they were given for.

This is the only monitor in the product line that prunes itself, and it does so because it has to. An identifier is often a company number, but it can equally be a sole trader's, and nothing in the identifier says which. Deleting your own monitor removes everything it held immediately.

## Billing and privacy

1 credit when a register answers. Free when it does not: a malformed identifier, one no register carries, an unsupported register and a register that is not installed all cost nothing, because none of them told you anything you could not have worked out. A repeat of the same identifier on the same account inside seven days is free.

A register that has not been synced answers `register_unavailable` and is never cached, so the next lookup answers as soon as the file lands. Absence of data is never reported as absence of the company.

Nothing about a single lookup is stored beyond the credit ledger entry that bills it. A monitor stores the identifiers you saved and the status of each from its last run, and nothing else. [POST /v1/entity/batch](https://spaw.co/docs/api/lookup-entity-batch) takes up to 500 identifiers in one call and bills the same way a single lookup does — only the ones that were found. There is no browser endpoint and no bulk upload for this product: the browser endpoints exist for a form a visitor fills in, and nobody types a company number into one.

Reference: https://spaw.co/docs/business-verification
