Skip to main content
This is the public contract. The models, serializers, API docs, and app all build against it. The response carries what you need to make a decision: the verdict, the reasoning behind it, and enough of the document’s identity to match it against your own records. Rendered revisions, highlighted diffs, and the raw structural analysis stay in the dashboard, where they can be displayed properly.

document_check

Flags and reasons

A flag is a category of finding. The set of flag codes is closed and stable. Branch on flag.code in your integration and it will keep meaning the same thing. A reason is one concrete observation that produced the flag. A flag always has at least one, and often several: two independent routes to the same conclusion is stronger evidence than one, so we return both rather than collapsing them.
reason.code is an open set. New reasons appear as detectors improve, without a version bump and without a new flag code. Display reason.summary; branch on flag.code.
flag.description is a one-line headline. flag.pages is the union of the pages named by its reasons.

status

While processing, verdict fields (decision, risk_score, summary, flags, …) are null/empty but the object shape is the same, so pollers never branch on which keys exist. That holds for nested objects too: every extracted field is present and null before extraction has run, rather than the block being empty.

decision

Assigned by the analysis engine (with score-band realignment after rule deltas):

risk_score

An integer from 0 to 100. Higher means riskier, in the same direction as risk_level. It is null until the check completes. decision is our default cut of that number, and the bands are fixed and published: Because the bands are contiguous and never move, decision is always recoverable from risk_score. The two can never tell you different things.

Setting your own line

Use action when you want workflow routing (approve / review / reject) to follow your appetite, or cut on risk_score yourself in your integration. action is not a decision and not a processing state: the verdict stays clear / pending / suspicious / fraudulent. Set cut lines via GET/PATCH /api/v1/thresholds/ or the Rules page. Those values are snapshotted onto each check so later edits do not rewrite history.
Within a band, the score reflects how much evidence there was. Findings combine by saturation rather than addition, so ten weak signals never outrank one decisive one, and a finding supported by several independent observations sits higher than the same finding seen once. A single HIGH finding on a fraudulent document scores around 92, high enough that the default reject line (70) and most tighter customer lines will treat it as reject. Triggered workspace rules also move the dial by their configured score_delta (positive = riskier). After that nudge, decision is re-aligned to the score band so the two stay consistent. Triggered rules are shown on the document viewer.
A clear document is still scored. Signals that were not strong enough to publish as flags (a rebuilt PDF, a named editing tool) still move it within 0–39, which is what gives you something to tune against across the documents that pass. This is why a clear result can score 18 while showing an empty flags array.
The score is comparable within an engine.version, not across versions. Retuning is what a version bump is for, so store engine.version alongside any threshold you set, and re-check your line when it changes.If a reviewer overrides a verdict in the dashboard, decision reflects the override while risk_score continues to report what the engine concluded. That is the only case where the two diverge, and it is deliberate.

What the API does not return

The original file, each stored PDF revision, the highlighted diff between revisions, and the raw structural dump are not part of the response. They are material for a human to look at, and that review happens in the dashboard, where every finding is rendered against the page it came from. You always keep the file you uploaded, and document.sha256 lets you tie a result back to it.