API
Three endpoints, one JSON shape, no SDK, no key and nothing to sign up for. Ten checks a day per address, counted the same whether they come from this page or from your terminal.
One call
curl 'https://check.scador.com/api/check?q=Gazprombank&country=RU' q is a company name or a 20-character LEI. country is an optional
ISO-3166 alpha-2 code; a wrong one is a 400 rather than a filter dropped in silence.
What comes back
{
"version": 1,
"query": { "q": "Siemens Aktiengesellschaft", "country": "DE" },
"verdict": "no-match",
"subject": {
"lei": "W38RGI023J3WT1HWRP32",
"name": "Siemens Aktiengesellschaft",
"country": "DE",
"status": "ACTIVE",
"registrationStatus": "ISSUED"
},
"matches": [],
"ownership": [
{
"lei": "W38RGI023J3WT1HWRP32",
"name": "Siemens Aktiengesellschaft",
"country": "DE",
"relation": "self",
"hops": 0,
"matches": []
}
],
"ownershipTruncated": false,
"checkedAt": "2026-09-19T21:01:06.980Z",
"sourcesAsOf": {
"OFAC": "2026-09-19T15:01:03.941Z",
"EU": "2026-09-19T15:01:03.943Z",
"UK": "2026-09-19T15:01:03.945Z"
},
"id": "z2wuPRQ739vo",
"permalink": "/c/z2wuPRQ739vo",
"ogImage": "/c/z2wuPRQ739vo/og.png"
} Every entry in matches — on the subject, and on each node of ownership — carries the reason it is there:
{
"source": "OFAC",
"program": "RUSSIA-EO14024",
"entryId": "TEST-DOC1",
"listedAt": "2022-02-24",
"countries": ["RU"],
"matchedName": "Gazprombank (Joint-stock Company)",
"score": 1,
"basis": "name",
"why": "name similarity 1.00 (\"gazprombank joint stock company\" ≈ \"gazprombank joint-stock company\") + same country"
} score is 0…1; anything reported is ≥ 0.85, or an exact LEI equality (1). permalink and ogImage are paths on this host — the shareable page and
its card.
basis is lei (an identifier, which cannot be a coincidence) or name (a resemblance, however strong). Only OFAC entries can ever produce basis: "lei": the EU consolidated list and the UK OFSI list publish no LEI at all, so a company that is
listed only in Brussels or London is reached by name resemblance and nothing else.
Read a name basis on those two accordingly — and read the absence of a match on
them as weaker evidence than the absence of one on OFAC.
The five verdicts
- LISTED
listedThis company itself appears on one of the sanctions lists we hold.
- PROXIMITY
proximityThis company is not listed, but a parent, owner or subsidiary within two ownership hops is.
- NO MATCH
no-matchNothing in this company or its GLEIF ownership matched the lists we hold. That is not the same as being clean.
- UNRESOLVED
unresolvedThe name fits several companies in GLEIF, so nothing was screened yet. Pick the one you meant.
- NO LEI
no-leiGLEIF knows no company by this name, so only the name itself was screened — with no ownership at all. The weakest answer this tool gives.
There is deliberately no clear. This service screens three lists and GLEIF
ownership; that is never proof that a company is clean.
The other endpoints
| Endpoint | What it does |
|---|---|
| GET /api/check?q=&country= | The check. Costs one against your daily quota. |
| GET /api/lei/{lei} | The same check addressed by identifier. Never answers unresolved — a LEI
cannot be ambiguous. |
| GET /health | Which lists this instance holds, when each last changed, whether GLEIF answered last time, and the running build. No quota. See it now. |
The check endpoints answer GET only. HEAD, POST, PUT, PATCH and DELETE are all refused with a JSON 405
and Allow: GET — HEAD in particular, because SvelteKit would otherwise serve it
by running the GET and dropping the body, which spends a check to produce no answer.
Limits
| Caller | Checks | Keyed on | Resets |
|---|---|---|---|
| Anyone | 10 a day | IP address (IPv6: the /64) | 00:00 UTC |
Every metered answer carries what you have left — a 429 included:
X-RateLimit-Scope: anonymous
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 9
X-RateLimit-Reset: 2026-09-20T00:00:00.000Z
Retry-After: 10734 (only on a 429 or a 503) And every 429 names the budget that ran out:
anonymous-day the day's ten for this address are spent An over-limit request still counts and is not refunded; a request that fails validation costs nothing. There is no account and no key: the address you call from is the whole identity, so a shared office address shares one day's ten.
POST /api/v is the page-view beacon used by this site — it is not part of the API,
it spends none of your checks, and it stores nothing you can read back.
When it refuses
400 { "error": "bad_request", "hint": "q: Too small: expected string to have >=1 characters" }
404 { "error": "not_found", "hint": "GET /api/check?q=…, GET /api/lei/<LEI>, GET /health — see /docs" }
405 { "error": "method_not_allowed", "hint": "GET /api/check — HEAD would spend a check for no answer" }
429 { "error": "rate_limited", "scope": "anonymous-day",
"limit": 10, "remaining": 0, "resetAt": "2026-09-20T00:00:00.000Z",
"hint": "You have used your 10 free checks for today. The counter starts again at 2026-09-20T00:00:00.000Z." }
503 { "error": "gleif_unavailable", "hint": "GLEIF API unavailable, retry later" } Every failure has the same shape — error is what code branches on, hint is for a human reading a terminal. One answer on this host is deliberately
not JSON: the CSRF refusal below, which SvelteKit emits before this app runs.
CSRF
A JSON POST needs no Origin header — send Content-Type: application/json and it is accepted from anywhere, which is what an
integration does. A form-encoded POST must come from this origin: browsers send the
header by themselves, and a scripted form post that omits it is refused with 403.
That refusal is the framework's, not this app's: it happens before any route runs, so it does not carry the {error, hint} shape. It is text/plain — the body is literally Cross-site POST form submissions are forbidden — unless the request sent Accept: application/json exactly, in which case it is {"message": "…"}. Either way the fix is the same: send the form from
this origin, or send JSON.