Developers API v1

The model

What an assessment is, what happens to it, and why the same request returns different evidence in different markets.

One idea organises this page. A market is described to the API as data, not as code. Everything that varies between countries is published on GET /v1/countries/{code}: which identifiers that market keys on and in what format, which classification schemes a rule can be written against, and which scale a rating arrives on. The request and response shapes do not vary. A client that reads the country resource at runtime works in the next market without new code.

Lifecycle#

Six states. Three are terminal.

State machine
POST /v1/assessments  →  202
      │
      ├─ mode: screening ──► screening ──► complete
      │
      └─ mode: full ──────► screening ──┬─► complete   (ended at the first stage)
                                        │
                                        ├─► awaiting_bank_data
                                        │         │
                                        │         │  POST /{id}/bank-data
                                        │         ▼
                                        └─────► processing ──► complete

      screening or processing ── cannot finish ──► failed
      awaiting_bank_data ── window elapses ──► expired
A policy that needs no bank data goes from screening straight to processing.
StateMeaning
screeningPublic registry sources are being gathered and the first-stage gates evaluated
awaiting_bank_dataThe first stage finished without a terminal outcome and the policy defines a condition that only bank data can evaluate. Never reached in screening mode
processingPaid sources and the full policy evaluation are running
completeTerminal. result is present
failedTerminal. Processing could not complete
expiredTerminal. Required input did not arrive, so the assessment closed without a result
A rejection is not a failure

reject is a policy outcome on a complete assessment: the system worked and the answer was no. failed means no assessment was produced at all.

Screening and full assessments#

screening runs the first stage and stops there. full continues into the rest of the policy. Evaluation stops at the first gate that fails, so a case rejected on eligibility never reaches anything further.

Which conditions and ratios make up the first stage is declared on the policy version and readable at GET /v1/policies/{version}. It is a property of the policy, not of the market.

Immutability#

Inputs are append-only. Results are written once.

POST /v1/assessments/{id}/bank-data is the single permitted append, and it is legal only in awaiting_bank_data. There is no endpoint that modifies a completed assessment, and there is no re-run: re-evaluating a case creates a new assessment carrying previous_assessment_id, and the earlier one keeps its result. A verdict acted on in one month still reads the same in the next, alongside the policy version that produced it.

previous_assessment_id records lineage and carries nothing forward. The new assessment fetches its sources again and evaluates every gate again.

Idempotency#

Send Idempotency-Key on every POST. Keys are retained 24 hours. Use one per logical case, not one per HTTP attempt.

You sendYou get
New keyThe request runs
Same key, identical bodyThe original response, replayed. No work is done, nothing is billed again
Same key, different body409 with idempotency_error
No keyThe request runs. A retry runs it again

Policy versions#

A policy version is a tenant-scoped string such as tenant-policy-2026-07, never a bare date. Every outcome names the version that produced it and every reason code repeats it. Versions are never edited in place, so a superseded version still describes exactly what it decided, and any outcome can be replayed against the rules in force when it was produced.

Missing data#

A missing value is a state, not an error, and you will meet it on a large fraction of real applicants. Every line item, ratio and indicator carries an availability saying why it is absent.

ValueMeaning
availableObtained
not_filedThe entity is legally exempt from filing it. The document does not exist
not_publishedNo public source for this entity type in this market
not_consentedNeeds a consent nobody holds yet
not_entitledNeeds a licence or statutory basis that neither the caller nor Credit Atlas holds
source_unavailableThe source exists and the call failed. The only one worth retrying
not_supportedNot implemented for this market yet

A ratio that cannot be computed returns band: null with the inputs it was missing. It never bands favourably, and the schema enforces that rather than describing it.

Evidence sufficiency#

An input that could not be obtained never improves an outcome. A sufficiency gate runs before the decision rules and can only cap the result, never raise it. It is judged per ratio group, so thin evidence in one group is visible rather than averaged away.

When too little could be computed to reach any conclusion, the outcome is insufficient_evidence. That is a statement about the inputs, not about the applicant, and it is not a rejection.

policy_outcome.capped_by says which happened: policy means the rules chose the outcome, evidence means thin evidence forced it. Those need different handling in your queue.

Capping is the normal path in some markets

In at least one market the typical company has only one of the three credit-risk ratios computable, because of what its size class is obliged to prepare. Build for a cap as the common case there, not the exception.

Policy checks#

A policy rests on two kinds of evidence. Ratios are one. The other is a set of discrete conditions: a rating floor, a default count, a sanctions result, a trading-history threshold, a revenue trend. Those arrive as checks[], each with its own availability and its own result.

A condition that was never evaluated reports not_evaluated, never pass. Sanctions screening that could not be performed is not a statement that the applicant is clear, and the contract will not let it be reported as one. A mandatory condition that could not be evaluated caps the outcome.

One condition can appear more than once. A condition run against each representative produces one entry per person, distinguished by subject, so a clean company with one problematic director is distinguishable from the reverse. Companies sent in related_companies are screened the same way, one entry per company.

Beneficial ownership is evidence you supply rather than evidence we gather. Access to those registers is restricted to entities with a statutory basis for customer due diligence, so where you hold that basis the lookup belongs on your side and its outcome goes in partner_data.beneficial_owners. An omitted block makes the condition not_evaluated rather than passing it.

Existing credit is reported alongside the checks. result.credit_exposure carries, per subject, the open facilities the bureau holds, the outstanding balance, the monthly instalments and the credit limits in use, with the facilities itemised where the bureau itemises them.

Check mandatory conditions against market coverage before you go live

If a policy marks a condition mandatory and no source in that market can evaluate it, every assessment there caps below the most favourable outcome. GET /v1/countries/{code} says what each market can answer, and policy_outcome.evidence_sufficiency.unavailable_mandatory_checks names the conditions responsible on any individual assessment.

Rating scales#

Markets do not share one rating scale. One publishes ordered letter grades; another a number where higher is better; another a number where lower is better, because it is a default probability rather than a quality grade.

Every scale a market uses is published in rating_scales on the country capability map, with its ordering and its direction, and every rating check names the scale it was evaluated on. A policy sets a floor per market, in that market's own terms.

Compare grades by position, never as text

On an ordinal scale the ranking is the order of grades, best first. Comparing the grades as strings ranks AAA below AA and B above AAA. Resolve through the scale.

Credit Atlas does not convert between markets’ scales, and a floor set in one market says nothing about any other.

Canonical keys#

Every market files financial statements to its own scheme, in its own language, at its own level of detail. Statement lines are normalised to a fixed vocabulary of canonical keys such as cash, current_assets, revenue and equity, and every ratio is defined on those keys. Adding a market is a mapping table between that market's filing scheme and the canonical keys.

Identifiers#

Published as patterns on GET /v1/countries/{code} so you can validate before sending rather than discovering the format from a 400.

MarketPrimary identifierNote
Estonia ^[0-9]{8}$ Eight digits, uniform
Latvia ^[0-9]{6}$|^[0-9]{8,9}$|^[3-5][0-9]{10}$ Do not assume eleven digits. Most modern registrations have eleven, but tens of thousands of legacy ones have six, eight or nine, and a strict eleven-digit rule rejects real company records
Poland ^[0-9]{10}$ Ten digits, checksum-validated. Companies also carry a separate court register number; sole traders do not

Transaction categories#

Every assessment reports coverage.bank_labels, which takes provider, classifier or not_supported.

Some account-information providers return a category per transaction. A raw open-banking feed returns a counterparty name, a free-text reference and nothing else. Any indicator defined over categories, such as lender payments, collections activity, round-value patterns or unrecognised credits, is unavailable in the second case unless a classifier has been built for that market's language and text.

Where categories exist, every transaction carries category_source, and the schema requires it on any transaction that carries a category. A threshold calibrated on provider labels must never be silently applied to classifier output.

Each indicator declares what it needs in requires: transaction_amounts, transaction_labels, counterparty_names, counterparty_jurisdiction, acquiring_data. You can read why an indicator is unavailable in a market from the indicator itself, before sending a request.

An uncomputed indicator reports triggered: null

false means the pattern was looked for and is absent. null means nobody looked, and it is never reported as false.