Skip to main content

Capture & Workflow API

The Capture & Workflow API runs a verification. The Company's backend starts a session with a chosen workflow; the capture front-end then walks through the steps the API hands back, one at a time, until the workflow completes.

There are two ways to use it:

ModeYou buildCovered in
Hosted appA backend that creates sessions. The hosted verification app renders every capture step.Website Redirect
Own front-endYour own UI — web or native — driving the API step by step.This page

Both modes share the same tenant configuration, the same sessions, the same workflows and the same result delivery. Only the user interface differs. Choose your own front-end when you need your own capture experience, branding beyond the hosted app's logo, or a native mobile app. In exchange you take on rendering every step, mapping error codes to your own copy, and handling retakes.

API reference​

The complete Capture API is published as an interactive reference — every endpoint, request and response schema, parameter, and status code — rendered directly from the service's OpenAPI specification.

Open the Capture API reference

Authentication​

The API uses OAuth2 client credentials. The token exchange must run on the Company's backend, not on the client device — putting credentials in the app would expose them. The token endpoint is OpenID Connect standard and expects a form-encoded body; see Website Redirect → Get an access token for a worked example. Access tokens are short-lived: cache for expires_in and refresh before expiry.

The resulting session token is what the client holds. It is passed as the X-Session-Token header on every subsequent call.

Session lifecycle​

The API is the state machine. Your client never decides what comes next — it renders the step it was given, captures, submits, and renders whatever comes back.

Start a session​

The backend calls POST /api/v1/session with the access token. The response is everything the front-end needs:

{
"sessionToken": "<jwt>",
"workflowId": "<workflow id>",
"nextStep": { "step": "document_front", "component": "DocumentCapture", "requiresFile": true },
"steps": [
{ "step": "document_front", "component": "DocumentCapture", "requiresFile": true },
{ "step": "document_back", "component": "DocumentCapture", "requiresFile": true, "skippable": true },
{ "step": "smile_capture", "component": "SmileLiveness", "requiresFile": true }
]
}
FieldUse
sessionTokenSend as X-Session-Token on every subsequent call.
workflowIdThe workflow actually resolved for this session.
stepsThe ordered, complete step plan — use it to drive a progress indicator.
nextStepThe step to render first.

Optionally the request carries configuration.locale, and — for workflows that verify against an existing Customer, such as authentication or personal data update — the target customer_id.

Pass the session token to your front-end over your own channel. Never expose the OAuth2 client secret or the access token to the browser.

Sessions expire and cannot be refreshed

A session is valid for the tenant's onboarding timeout, 300 seconds by default. When it expires the server-side session goes with it. There is no refresh — start a new session instead.

Read branding and the step plan​

GET /api/v1/info, authorized with X-Session-Token, repeats the step plan and adds the configured locale and logo URL.

It is optional — the session response already carried steps and nextStep. It is useful when a client has to bootstrap without that response, most commonly the phone that picks up a session after a mobile hand-over.

The submit loop​

POST /api/v1/submit
X-Session-Token: <sessionToken>
Content-Type: multipart/form-data

file=<captured-bytes>
The client never sends a step name

The platform reads the current step from the session. Which step a submission applies to is determined entirely by the last nextStep you received, so a step field in the body has no effect.

All body fields are optional:

FieldMeaning
fileCaptured bytes for the current step. Required when the step's requiresFile is true, except for the challenge-issuing phase of a two-phase step. Accepted as a binary part, a base64 string, or a data:<mime>;base64,… URL.
skipStepsInteger ≥ 1 — advance that many steps. Every step traversed must be skippable.
canceltrue abandons the session. Captured data is deleted and the session terminates.
redirectToken · clearRedirectTokenMobile hand-over only.

A submit with no fields at all is a safe no-op that returns the current step. Use it to re-read state without uploading anything.

File constraints, enforced identically on every capture step:

  • Maximum 15 MiB; larger uploads fail with FILE_TOO_LARGE.
  • Accepted types: image/jpeg, image/png, image/webp, video/mp4, video/webm, video/quicktime, application/octet-stream.
  • Document, selfie and palm steps expect still images. Active liveness steps expect the video blob produced by the capture component — do not transcode it to a still. NFC sends the raw chip response as application/octet-stream.

Step catalogue​

Every nextStep.step and every entry in steps[] is one of the ids below. Which ones appear depends on your tenant's workflow.

Step idcomponentFileNotes
mobile_redirectMobileRedirectNoCross-device hand-over. Skippable when the workflow says so.
document_captureDocumentCaptureYesSingle-page document.
document_front · document_backDocumentCaptureYesTwo-page document. document_back is skippable unless the workflow disables that.
face_captureSelfieCaptureYesDropped from the plan when the workflow also contains an active-liveness step.
smile_captureSmileLivenessYesActive liveness video.
multirange_captureMultiRangeLivenessYesActive liveness video, two-phase.
nfc_captureNfcCaptureYesChip read, two-phase.
both_palms_capture · single_palm_capturePalmCaptureYesPalm verification.
external_captureExternalCaptureNoTenant-defined pass-through; payload arrives in nextStep.data.

When the same action appears more than once in a workflow, later occurrences take a numeric suffix — document_front_2, both_palms_capture_2. Treat unknown ids defensively: render by component where you can, and fail loudly rather than skipping a step silently.

Server-side stages — trust factor evaluation, duplicity checks, matching, the decision — never appear in steps[]. They run after your last capture, which is why a session can end in a pending state rather than a final one.

Two-phase steps​

Multi-range liveness and NFC need a server-issued challenge before capture:

  1. Submit with no file on that step. The response keeps nextStep.step on the same step and carries the challenge in nextStep.data.
  2. Run the capture with that challenge, then submit the resulting file on the same step.

Each challenge is single-use — request a fresh one before every retry. When the flow advances into one of these steps the challenge is attached automatically, so the extra round trip is only needed if you lost it or bootstrapped from GET /api/v1/info.

Step data payloads​

StepnextStep.data
multirange_capture{ "challengeSequence": [...] } — the ranges to drive the capture
nfc_capture{ "challenge": "<base64 challenge>" }
both_palms_capture{ "palmCapture": { "phase": "awaiting_second_palm", "capturedHand": "left", "expectedHand": "right" } }
external_captureTenant-defined object from the workflow configuration
terminal steps{ "customer_id": "…", "identity_id": "…" } when available

Handing over to a phone​

The mobile_redirect step moves a session from a desktop browser to the user's phone, typically by QR code. The phone joins with a redirect token and bootstraps its UI from GET /api/v1/info. The session, and its step plan, continue where the desktop left off.

Terminal steps​

The loop ends when nextStep.step is one of:

StepMeaningWhat to do
completeVerification succeeded.Show success. data.customer_id / data.identity_id identify the record.
enrolment_pendingAll captures are in; the outcome is still being decided, for example a manual review.Show a pending state. The final outcome arrives by callback.
failThe run ended without a positive result — cancelled, rejected, or a mandatory step exhausted its retake budget.Stop submitting. Start a new session if the user should try again.

Stop the loop on all three. Submitting again on a terminated session keeps returning fail.

Skipping and cancelling​

Send skipSteps: <n> to move forward without capturing, over steps whose skippable is true — typically the back page of a document, the mobile hand-over, and external capture. Skipping a mandatory step returns STEP_NOT_SKIPPABLE; going past the end returns INVALID_SKIP_STEPS.

Send cancel: true to abandon the session. Captured data is deleted and the session terminates.

Retakes, errors and limits​

Three different things can come back, and they are handled differently.

Quality retakes​

A capture that was not good enough is not an error. It arrives as a retake object with no error, carrying a reason code, the retake count and the limit, and it may rewind nextStep to an earlier step:

{
"nextStep": { "step": "document_front" },
"retake": {
"reason_code": "LOW_OCR_CONFIDENCE",
"retake_count": 1,
"retake_limit": 5
}
}

Re-prompt the user with guidance matching the reason. Exhausting the budget ends the session. Reason codes and the underlying checks are configured in IDV Configuration → Document OCR Quality.

Business errors​

These return HTTP 200 alongside a nextStep. They mean "that capture cannot be used" — re-prompt and resubmit on the same step:

{
"nextStep": { "step": "document_front" },
"error": {
"code": "DOCUMENT_CAPTURED_INCORRECTLY",
"message": "Document corners were not detected",
"retryable": true,
"requestId": "…"
}
}
tip
Branch on retryable, not on the code

retryable: true means recapture. retryable: false means the run is over and nextStep.step is terminal. New codes are added over time; a switch over codes will eventually meet one it does not know, but retryable stays meaningful.

CodeMeaningRetryable
MISSING_FILEThe step requires a file and none was sent.Yes
DOCUMENT_NOT_RECOGNIZEDThe document could not be read or classified.Yes
DOCUMENT_NOT_ALLOWEDDocument type not permitted for the tenant; details.allowed lists what is.Yes
DOCUMENT_CAPTURED_INCORRECTLYCorners missing, wrong page, or a back page that does not match the front.Yes
BAD_LIGHTNING_OR_LOW_QUALITYImage or liveness quality too low, or no face detected.Yes
UNABLE_TO_VERIFYThe facial data could not be processed.Yes
PALM_CAPTURE_REJECTEDPalm capture rejected — wrong side, wrong hand, liveness or injection.Yes
PALM_REQUIRED_NOT_CAPTUREDPalm retake budget exhausted.No
NFC_REQUIRED_NOT_CAPTUREDNFC retake budget exhausted.No
INVALID_STEPThe session is not on a step that accepts this submission.No
INVALID_SKIP_STEPSskipSteps out of range.No
STEP_NOT_SKIPPABLEA step in the skipped range is mandatory.No

Transport errors​

A non-200 status with the same envelope and no nextStep:

HTTPCodeMeaning
400INVALID_REQUEST_BODYMalformed request.
400FILE_TOO_LARGE · INVALID_FILE_TYPESee the file constraints above.
401SESSION_EXPIREDSession token expired or invalid — create a new session.
404NOT_FOUNDThe session no longer exists.
429RATE_LIMIT_EXCEEDEDAnti-hammering budget exceeded — back off.
5xxService unavailableVerification backend unavailable — retry the submission.

Session creation has its own failures, none of which are retryable as sent: no workflow configured, a workflow that requires a customer_id that was not supplied, no enrolled palm for the named Customer, and the Sandbox verification limit reached.

Every error carries a requestId. Log it — Innovatrics support needs it to trace a failure.

Getting the result​

Without the hosted app there is no redirect and no postMessage, so the outcome reaches you three ways:

  1. Callback — authoritative. Your configured callback URL receives the verified customer data, signed, exactly as in the hosted flow. Drive your own state from this, not from the front-end.
  2. Terminal step data. complete and enrolment_pending carry customer_id and identity_id, so a client without a callback receiver can still identify the record and fetch it from the Customer API.
  3. Session-scoped read. While the session is still valid, the result endpoints return what was captured, authorized by the session token alone. Useful for a review screen inside your own UI.
Never trust a front-end-reported outcome

The session token lives in the browser or the app, where the user controls it. Confirm every verification server-side from the callback before granting access, creating an account, or releasing funds.

See also​