Skip to content

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, and type is either 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.

Every field

field type meaning
identifierstringThe identifier as the register writes it: a LEI uppercased, a company number padded to eight characters.
identifier_typestringlei or company_number, echoing what was asked for.
foundbooleanWhether a register carried it. False is free, and never means the entity does not exist — only that this server could not answer.
reasonstring | nullWhy not: unknown_identifier, malformed_identifier, unsupported_register or register_unavailable. Null when found is true.
namestring | nullThe legal name the register carries.
statusstring | nullWhether the entity is still live, folded to active, inactive, dissolved or unknown.
status_detailstring | nullThe register’s own wording, which distinguishes a voluntary strike-off from a liquidation.
country / jurisdictionstring | nullThe country of registration, and the jurisdiction of formation, which can be a subdivision such as US-DE.
category / legal_formstring | nullThe register’s entity category, and the ISO 20275 entity legal form code where one is carried.
registered_atdate | nullWhen the entity was formed or incorporated.
record_updated_atdate | nullWhen the register last updated this record.
checksum_validboolean | nullLEI only: whether the ISO 7064 MOD 97-10 check digit is right. A typing check, not proof the identifier was issued. Null where the identifier carries no check digit.
registration_statusstring | nullLEI only: ISSUED, LAPSED, RETIRED, ANNULLED, MERGED or DUPLICATE. An entity can be alive while its LEI is not.
next_renewal_atdate | nullLEI only: when the registration was last due for renewal.
other_register / other_identifierstring | nullThe national register this record names, and this entity’s identifier there — which is what makes the cross-register comparison possible.
flagsstring[]What the registers disagree about, or what one of them warns. Each has its own reference page.
agreementsobject[]Each cross-register comparison with both sides quoted. Empty where there is no counterpart record.
sourcesobject[]Every register behind the answer, with the date of the copy this server holds.

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. A record can therefore 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 and each comparison quotes both sides.

  • Name— compared on letters and digits with the company-form words removed, so “ACME HOLDINGS LTD.” and “Acme Holdings Limited” agree. A real difference raises name_differs_across_registers.
  • 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, so a disagreement usually means the LEI record is stale.
  • Dissolved— 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 whether this is a real, live entity, and holds no register of where people live.

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.

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.

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: dissolved, inactive, registration_lapsed , registers_disagree or left_register. An identifier no register could answer for is counted unverifiable and never alerted on — that is a limit of the data here, not a fact about the company.

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

So 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 a status of 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.

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 X-Spaw-Signature-V2 carries 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, is sent beside it until 1 March 2027 and still verifies. 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} 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.

How long a monitor is kept

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