A company does not become a risk on the day you look it up.
You verified it at onboarding. Since then a director resigned, an owner crossed 25% through a holding company, and an operator licence ran out. Send the record you hold (with the record you held last month) and get back the changes that are material, an effective-ownership chain multiplied all the way down, and a risk score whose every point is a named signal with a published weight.
Runs the same engine the paid endpoint uses, on the data in this box. Nothing is stored and no key is needed.
Press “Assess this entity” to call the live API.
Verification is a photograph. Risk is a film.
Onboarding checks a company once, writes down an answer, and moves on. The company keeps changing. The three things most likely to change are the three hardest to notice from a flat record.
The owner is not on the share register
Somebody holding 55% of a company that holds 60% of your counterparty controls 33% of it (over every beneficial-ownership threshold there is) and their name appears nowhere on the counterparty's own cap table. Finding them means multiplying percentages down a chain that nothing in a normal stack multiplies.
A re-check produces a diff, not an answer
Pull the record again and almost everything differs: a postcode reformatted, a name re-typed, a shareholder up a point. Somewhere in that list a director resigned. Without a rule for which differences matter, the alert queue trains people to ignore it.
Dates fail quietly
A licence has an expiry date and a status field, and they disagree: the status still says active a month after the date passed. Nothing errors. The permit simply is not valid any more, and the first anyone hears of it is the counterparty's regulator.
Nothing alerts. The exposure just grows.
None of these is a system failure. Each is a fact about a counterparty that was true for months before anyone in your business could have known it.
-
Ownership
A beneficial owner you never named
Your file lists the holding company. The regime asks about the natural person at 25% or more. They are there (three quarters of the way up a chain of two) and the only thing between you and naming them is arithmetic nobody ran.
-
Status
Credit extended to a company in administration
The status changed in a filing. Your record still says active, because your record is a copy taken on the day you onboarded. Limits keep renewing against an entity whose control has passed to an insolvency practitioner.
-
Licence
A permit that lapsed between annual reviews
Licences expire on a date and are reviewed on a cycle, and the two are not aligned. Between the lapse and the next review the counterparty is operating unlicensed: and so, in the part of the transaction that is yours, are you.
Five answers a flat record cannot give you.
Send the entity data your systems already hold. There is no registry lookup, no data vendor behind this and no model guessing: you supply the record, and the engine does the arithmetic deterministically, the same way every time.
| Output | What it answers | Why it is hard by hand |
|---|---|---|
| Effective ownership | Who really owns this, once the chain is multiplied out. | Percentages compound; floats drift across a 25.00% test. |
| Material changes | Which of the differences between two snapshots matter. | Threshold crossings matter; raw deltas usually do not. |
| Risk signals | Every named concern, with the points it contributed. | A score with no breakdown is one nobody will act on. |
| Licence expiry | Days remaining, and whether the licence is usable today. | Status and expiry date disagree, and timezones bite. |
| Structure warnings | Loops, unattributed share, holdings that lead nowhere. | A cycle makes a naive traversal run forever. |
Entity record Validated Ownership chain Signals Material changes Into your queue
-
01
The record in, checked
Registration, status, officers, holdings, licences. Every percentage is an integer number of basis points; a subject that ends up more than 100% owned is a
400naming the field, not an assessment nobody should act on. -
02
Ownership multiplied down the chain
Each holder's stake is the product of the holdings on every path to the entity, summed across paths. The arithmetic is exact integer arithmetic, not floating point, so a holder sitting on exactly 25.00% is reported on the same side of the line every time.
-
03
Loops found, depth bounded
Circular ownership is real and a naive walk through it never stops. Loops are detected, reported as a path, and cut. Depth and total work are bounded, and if a bound stops the walk the output says so rather than looking complete.
-
04
Signals fired, weights published
Each finding is a stable code with a documented weight and a plain-language detail. The score is the sum of the weights of the codes that fired, capped at 100.
GET /v1/risk-signalsreturns the whole table, so you can re-derive any score by hand. -
05
Changes classified
Send last month's snapshot too. Every difference is reported, and each is marked material or not: a director resigning and an owner crossing 25% are material; a postcode reformatted and a stake moving 5% to 6% are not.
-
06
Whole UTC days, everywhere
Licence expiry, officer tenure and entity age are computed on whole UTC days. An officer is not in post on their resignation date, and a licence is still valid on its expiry date: both stated, both tested, neither dependent on the server's clock.
Two snapshots in. Six signals, two material changes, one hidden owner.
The record in the demo panel above, as returned by POST /v1/assessments. Peter Vance holds nothing directly and controls a third of the company; last month he was one basis point under the line. Abridged: the full response also carries every officer, every licence and the band table.
{
"entityId": "ent_88213",
"score": 67, "band": "high",
"scoring": {
"contributions": [
{ "code": "ownership_unallocated_major", "weight": 18 },
{ "code": "recent_ownership_shift", "weight": 15 },
{ "code": "recent_officer_departure", "weight": 12 },
{ "code": "indirect_beneficial_owner", "weight": 8 },
{ "code": "licence_expiring_soon", "weight": 8 },
{ "code": "sole_officer", "weight": 6 }
],
"rawPoints": 67, "score": 67, "capped": false, "band": "high"
},
"ownership": {
"thresholdBasisPoints": 2500,
"owners": [
{ "holderName": "Meridian Holdings BV", "effectivePercent": "60.00%",
"directBasisPoints": 6000, "beneficialOwner": true, "indirect": false },
{ "holderName": "Peter Vance", "effectivePercent": "33.00%",
"directBasisPoints": 0, "beneficialOwner": true, "indirect": true },
{ "holderName": "Amara Okonjo", "effectivePercent": "15.00%",
"directBasisPoints": 1500, "beneficialOwner": false, "indirect": false }
],
"unallocatedBasisPoints": 2500, "cycles": [], "truncated": false
},
"licences": [
{ "licenceId": "lic_opr_2211", "daysRemaining": 14,
"expiry": "expiring_soon", "effective": true }
],
"changes": [
{ "code": "officer_resigned", "material": true, "subject": "Tom Brennan",
"detail": "Tom Brennan (director) is no longer in post — resigned 2026-08-12." },
{ "code": "ownership_threshold_crossed_up", "material": true,
"before": 2400, "after": 3300, "thresholdsBasisPoints": [2500],
"detail": "Peter Vance crossed 25.00% upward: 24.00% to 33.00% effective ownership." }
],
"changeSummary": { "total": 2, "material": 2, "informational": 0,
"hasMaterialChange": true }
}
18 + 15 + 12 + 8 + 8 + 6 = 67
That is the entire model. The score is the sum of the weights of the distinct codes that fired, capped at 100: no hidden term, no curve. A code counts once however many instances triggered it, so three expired licences are one expired-licence problem, not ninety points.
24.00% to 33.00%, not "+9%"
Peter Vance held 40% of a 60% holder, then 55% of it. Neither number is a direct holding, and the event worth an alert is not the size of the move: it is that a line at 25.00% was crossed. A holder going 5% to 6% is reported too, marked material: false.
You supply the record
Business Risk queries no registry, calls no data vendor and holds nothing about any real company. It assesses the fields in your request. The output is reproducible arithmetic on your data: not a credit decision, and not a legal determination.
This service is newly launched. We publish no uptime percentage, customer count, logo wall or certification claim until we have the operating history to support one.
Three steps to continuous monitoring
Assess the record you hold
POST the entity. Back come weighted signals, a score, effective ownership through the whole chain and per-licence expiry. One unit per entity, up to 100 per call.
Keep the snapshot
Store the record you sent. Next cycle, send the new one and the old one together. Nothing is stored here, so the snapshot you keep is the only state in the system: and it stays in your database.
Route on
hasMaterialChangeOne boolean decides whether a human looks. Under it, every change carries a stable code, a materiality flag and a plain-language detail you can put straight in a case note.
Integer percentages, UTC days, bounded walks
- Ownership is integer basis points, 10000 = 100%. A chain is multiplied exactly and rounded once, at the end: rounding at every hop reports stakes that are not there.
- A crossing is the event, not the delta. 24.99% to 25.00% is material. 5% to 6% is not. Both are returned; only one is flagged.
- Dates are whole UTC days. An officer is not in post on their resignation date. A licence is valid on its expiry date.
- Every walk is bounded. Depth, total steps and cycles all have explicit limits, and hitting one is reported as a signal rather than hidden behind a plausible-looking answer.
- Unknown is not fine. A status of
unknown, an empty officer list and unattributed share all score. Absence of evidence is scored as absence of evidence. - Output is deterministic. Same record, same evaluation instant, same bytes: so a stored assessment can be diffed against the next one.
- Nothing is stored. Every endpoint is stateless. The entity data you send is assessed and dropped.
Three endpoints, versioned, OpenAPI-first
Authenticate with Authorization: Bearer <key>. Every error returns a stable code and a requestId you can quote, and validation failures name the exact field in details.path.
/v1/keysPublic. Emails a one-time claim token to the address you give it. No key is returned on this call, proving the mailbox is what stops anyone minting a key on your account./v1/keys/claimPublic. Spends the emailed token and returns the key, shown once: 100 assessments a month, no card./v1/assessmentsAssess one entity or up to 100, with an optional previous snapshot each. Billed one unit per entity./v1/demo/assessPublic. Real engine, one entity, capped at 25 officers and 25 holdings. No key required./v1/risk-signalsPublic. Every signal code with its weight, the band cut-offs and every material-change code.curl -X POST https://businessrisk-api.com/v1/assessments \ -H "Authorization: Bearer $BUSINESS_RISK_KEY" \ -H "content-type: application/json" \ -d '{"entity":{"entityId":"ent_88213", "legalName":"Harbourline Logistics Ltd", "jurisdiction":"GB","status":"active", "officers":[{"officerId":"off_1","name":"Amara Okonjo", "role":"director"}], "ownership":[ {"holderId":"hold_meridian","holderType":"entity","basisPoints":6000}, {"holderId":"ind_vance","holderType":"individual", "subjectId":"hold_meridian","basisPoints":5500}]}}'
Python and TypeScript SDKs Service health: https://businessrisk-api.com/health
Priced per entity assessed, capped so a portfolio sweep can’t surprise you
One unit is one entity assessed: with or without a previous snapshot, because monitoring a company should not cost twice what checking it once does. Every plan includes an allowance, then a per-unit overage that is hard-capped per billing period: your worst-case bill is the base price plus the cap, never more.
- 1,000 entity assessments included
- $0.75 per additional entity assessment
- Overage capped at $1,000 per period
- Full API access, Python and TypeScript SDKs
- 5,000 entity assessments included
- $0.55 per additional entity assessment
- Overage capped at $4,000 per period
- Full API access, Python and TypeScript SDKs
- 20,000 entity assessments included
- $0.35 per additional entity assessment
- Overage capped at $10,000 per period
- Full API access, Python and TypeScript SDKs
- 60,000 entity assessments included
- $0.25 per additional entity assessment
- Overage capped at $20,000 per period
- Full API access, Python and TypeScript SDKs
Free tier: 100 entity assessments per month, no card. Prices in USD, billed monthly. Payment is processed by Square through our shared billing platform; card details never touch this site.
Is this a company data source?
What counts as an assessment?
What makes a change "material"?
GET /v1/risk-signals. Broadly: threshold crossings, control-role officer changes, status transitions, jurisdiction moves, identity-field changes and licence lapses are material. Stake movements that cross nothing, address changes inside the same country, and re-typed names are not. Every difference is returned either way, with a material flag, so you can apply your own rule instead.Why basis points instead of percentages?
Can I change the thresholds?
Can I evaluate it before paying?
Find the change before it finds your loan book.
The free tier is 100 entity assessments a month, no card. Create a key, post one entity record, and the response names every owner the chain hides.
One email address, no password, no card. Prefer the terminal? The same key, in two calls:
curl -X POST https://businessrisk-api.com/v1/keys \ -H "content-type: application/json" \ -d '{"email":"you@company.com","name":"Evaluation key","source":{"source":"web","medium":"landing"}}' # 202 Accepted. A one-time token is emailed to that address — spend it, # and the response carries the key, shown once. curl -X POST https://businessrisk-api.com/v1/keys/claim \ -H "content-type: application/json" \ -d '{"token":"<token from the email>"}'