2026-09-08
Business verification, and cross-signal consistency
Two new endpoints: POST /api/v1/entity checks a Legal Entity Identifier or a UK company number against registers synced locally, and POST /api/v1/consistency runs any subset of email, phone, IP and address and names where they agree and where they do not.
POST /api/v1/entityis the fifth product: a Legal Entity Identifier read against the GLEIF Level 1 golden copy, or a UK company number read against the Companies House free company data product. Both are bulk files synced into a local table, so no third party sits in the request path. The guide is at/docs/business-verification.- It answers the legal name, whether the entity is still live (
active,inactive,dissolved, with the register's own wording instatus_detail), the country and jurisdiction of formation, the entity category and legal form, and the incorporation date. For a LEI it addschecksum_valid— the ISO 7064 MOD 97-10 check digit from ISO 17442 — plusregistration_statusandnext_renewal_at. - A LEI can lapse while the company behind it is perfectly alive, so the two facts are separate:
statusis about the entity,registration_statusis about the identifier, andlei_registration_not_currentflags the difference. - Where a LEI names a Companies House number and both registers are installed, the two records are compared field by field.
agreementsquotes both sides andflagsnamesname_differs_across_registers,status_differs_across_registersanddissolved_in_national_register. Names are compared on their letters and digits with the company-form words removed, so "ACME HOLDINGS LTD." and "Acme Holdings Limited" agree. - The checksum is reported beside the register answer rather than standing in for it. Measured against the published register: of 45,515 consecutive real LEIs, 45,512 satisfy the check digit, and all three that do not are annulled registrations.
- No registered address is stored or returned, and none will be. A registered address is very often somebody's home, and an identifier alone does not say whether a limited company or a sole trader stands behind it. The product carries country and jurisdiction, which answer whether this is a real, live entity without holding a register of where people live.
- Monitored lists: save up to 500 identifiers and have them re-checked weekly or monthly (no daily cadence — the UK company file is republished monthly). A run answers every identifier
ok, one ofleft_register,dissolved,inactive,registration_lapsedandregisters_disagree, orunverifiablewhere no register could answer at all. left_registerexists because of how the UK register actually works: the Companies House free product is a snapshot of the live register, so a company that is struck off is removed from the file rather than marked dissolved. A dissolution therefore reaches you as the identifier disappearing between runs, and reporting that asunverifiablewould have let the single most important event pass silently. An email goes out only for an identifier that was fine last run and is not now; the first run only sets the baseline.- Every monitor now prunes itself, across all five products. A saved list that stops being re-checked is deleted along with the identifiers and the results it stored — an email monitor, a phone monitor, an IP monitor, an address monitor and a business monitor alike. A list in active use is never touched, because each run advances
last_run_at, so the window only closes on one that has genuinely stopped. Retention is 365 days by default and configurable per product,prunes_after_daysis on every monitor payload andmeta.retention_dayson every list endpoint, so the rule is visible in the API rather than only in the docs. - The business monitor is where the reasoning started: an identifier is often a company number but can equally be a sole trader's, and nothing in it says which. The same argument holds for an email address, a phone number, an IP and a postal address, which is why the rule was extended to the other four rather than left as a special case.
- 1 credit when a register answers. A malformed identifier, one no register carries, an unsupported register and a register that is not yet installed are all free — and a register that is not installed says so, rather than reporting that the company does not exist.
- Data: the LEI reference data is published under CC0 1.0 and refreshed daily; the Companies House file is Open Government Licence v3.0 and republished monthly, so an answer can be up to five weeks behind the live register. Every answer names both registers with the date of the copy, and
/statuslists them. Spaw is not affiliated with or endorsed by the organisation that publishes the LEI data. POST /api/v1/consistencytakes any subset ofemail,phone,ipandaddress, runs each through the product that owns it, and returnsdata.signalswith all four keys always present — each holding that product's full answer, field for field, ornullfor a signal you did not ask for. The signals run in a fixed order (ip,phone,address,email) because a JSON object has no input order, and because email is the only leg that does DNS and a mailbox handshake. The guide is at/docs/cross-signal-consistency.- On top of them, a free
consistencyblock:country_agreementover the phone, address and IP countries, and eight comparisons that each quote both sides — the signal, the field and the value that decided it — so nothing is asserted you cannot see the working for. - Seven flags, each with its own reference page:
phone_country_differs_from_address_country,ip_country_differs_from_address_country,ip_country_differs_from_phone_country,disposable_email_from_datacenter_ip,disposable_email_from_anonymous_network,mail_drop_address_with_free_email_provideranddisposable_phone_with_disposable_email. They sit in aflagfield rather than areason, because they describe two signals rather than one. - A comparison is emitted only when both of its signals answered. Where one side is null it answers
not_comparedwithwithheld: "missing_value"— a null means not evaluated, never no. Where the IP is a VPN exit, a relay or a datacenter egress, the country comparisons are withheld aslow_confidence: that country belongs to the network operator, and reporting a difference would flag everyone who uses a VPN. - No score, no risk level, no verdict and no recommendation, and there will not be one. A difference between two fields is a fact about the fields, not a judgement about a person — people travel, move house, keep a foreign number and browse privately.
- No new dataset, licence or sync command: every comparison is arithmetic over fields the four pipelines already computed. Each signal bills exactly as its own single lookup,
meta.credits_usedis the sum, and the analysis is free. Running out part-way keeps what was paid for and reportsmeta.stopped_reason. - The MCP server gained
verify-entityandcheck-consistency, and the Python, Node and PHP SDKs gainedcheck_consistency/checkConsistency. Keys carry a newconsistencyscope, and the endpoint has its own rate-limit bucket, since one call fans out to as many as four lookups. - A free
/tools/card-number-checkerjoins the tool pages: brand, Luhn check digit and permitted length, computed entirely in the browser — no request, no endpoint, no log line, and the number never appears in the URL. It makes no issuer, bank, country or debit/credit/prepaid claim, and says plainly why: there is no free, openly-licensed, authoritative source for those, so it answers nothing rather than guessing. - Fixes: a mailbox probe lost to a provider outage is now free whatever names the verdict — a role address on an accepts-mail domain was still being charged during an outage, against the published rule. The French national address register (Base Adresse Nationale) is installed alongside the other five, and the Swiss register's coverage no longer claims Liechtenstein, which its file has never contained.
markdown version: /changelog/2026-09-08-business-verification.md · atom feed: /changelog.atom