Document verification

Verify a person from a photo of an identity document, for someone who has no registry route. The result is the same claims-only VerifiedIdentity as the registry flow, with an honest assurance level — never a decision.

Early access

Document verification is in early access until it is generally available; the date will be announced on the changelog. Request access by writing to sales@emnetid.com.

What it can and cannot prove

A label on the wire says only what was proven. A photo of a document proves nothing about whether the document is genuine — any photo, screen or edited image can produce one — unless the document carries a signature that can be checked. So:

  • On LIVE keys, only the Fayda card can be verified, through the signed QR code on its back (DOCUMENT_SIGNED_QR). In LIVE only this signed tier can complete, and only once it is enabled for your account.
  • Passports, MRZ identity cards, US driver licences and kebele IDs are never verified on LIVE. For them, use document data capture: fields to prefill a form a person confirms, with no claim of authenticity.
  • Any environment Emnet hosts applies the LIVE rule to TEST keys too. A hosted sandbox starts only Fayda-card sessions and completes only a verified signed QR, so what you build against it behaves the way it will in production.
  • On a LIVE key, any outcome other than a verified signed QR is 400 VERIFICATION_FAILED — never a weaker tier relabelled as a pass.

Assurance levels

assuranceLevelWhat was provenLIVE?
DOCUMENT_SIGNED_QRThe Fayda card’s signed QR code was verified.Yes, once enabled for your account
DOCUMENT_MRZ_CHECKEDEvery machine-readable-zone check digit verified without correction — the document is self-consistent, not authentic.No — TEST only
DOCUMENT_BARCODE_PARSEDA licence barcode was decoded. It is unsigned.No — TEST only
DOCUMENT_OCRPrinted text was read.No — TEST only

The three unsigned tiers prove nothing about authenticity. No environment Emnet hosts returns them; they are in the contract so that your code can name and handle them. assuranceLevel is a string, not a closed list: treat any value you do not recognise as not verified.

Who does what

StepWhereKeyOperation
1. Start a sessionYour backend (or a native app)sk_ (or pk_ in a native app)startDocumentVerification — returns sessionId, the device’s sessionHandle, a one-time evidence token, the parts to photograph and the accepted upload-key thumbprints.
2. Encrypt and upload each photoThe devicepk_uploadVerificationEvidence, through the document-capture package — the same encrypted envelope and key pinning as data capture.
3. Complete and receive the claimsYour backend onlysk_completeDocumentVerification — returns the VerifiedIdentity.

Start is not available to browsers (there is no cross-origin access to it), and completion is never available to a browser or a device: a secret key belongs on your backend only. A web app starts the session on its backend and hands the Start response to the page.

The flow

1. Start

POST /v1/verification/person/document/sessions with {"documentType": "FAYDA_DIGITAL_ID"}. The document type must be enabled for your account.

json
{
  "sessionId": 4711,
  "sessionHandle": "ds_0f3c9a1b7d2e4f60a8b9c0d1e2f3a4b5",
  "evidenceToken": "…",
  "evidenceTokenExpiresAt": "2026-10-10T09:15:00Z",
  "requiredEvidence": ["FRONT", "BACK"],
  "acceptedEvidence": ["FRONT", "BACK"],
  "uploadKeyThumbprints": ["…"],
  "environment": "TEST"
}

sessionId is your address for the session; it also works with Read a session. sessionHandle is the device’s address. The evidenceToken is returned once: hand it only to the device that uploads and keep it for completion. Never log it and never put it in a URL.

2. Upload each part, encrypted on the device

POST /v1/verification/person/document/sessions/{sessionHandle}/evidence, exactly like a capture upload: the same evidence envelope, the same pinned upload keys, the same browser rules, any part in acceptedEvidence, each part once. Nothing is verified at upload. The document-capture package recognises a verification Start response and uploads to the right place.

3. Complete — on your backend

POST /v1/verification/person/document/sessions/{sessionHandle}/complete with the evidence token and a secret key.

AnswerMeaningWhat to do
200Completed; the evidence is destroyed.Read the VerifiedIdentity (below).
400 VERIFICATION_FAILEDThe document could not be verified and the session is finished. The response never says why.Do not retry the session.
422 EVIDENCE_UNREADABLEA retake, not a failure. The session and its token are kept, but every uploaded part has been destroyed. retakeHint says why (image quality only).Re-upload EVERY part, then complete again. Retakes are limited; after the last one the session fails.
409 EVIDENCE_INCOMPLETEA required part has not been uploaded yet; nothing changes.Upload it, then complete.
503 PROVIDER_UNAVAILABLETemporarily unavailable.Retry with back-off before the token expires.

With the document-capture package, a retake means a new uploader created from the same Start response, which sends every part again.

No answer is not a failure

A 504 or a dropped connection on the completion call does not mean the request failed: the server may already have consumed the one-time evidence token and destroyed the evidence. Do not retry the same call expecting a result — a retry answers 404. Start a new session, or ask the device for a retake.

The result

json
{
  "sessionId": 4711,
  "identityKey": "3f9c1a0b7e5d4c2a9b8e7f6d5c4b3a29…",
  "identityKeyType": "DOCUMENT_PSEUDONYM",
  "claims": {
    "name": "…",
    "name_am": "…",
    "birthdate": "…",
    "document_type": "FAYDA_DIGITAL_ID",
    "document_issuing_country": "ET",
    "document_number_last4": "…"
  },
  "claimConfidence": { "name": 0.95, "name_am": 0.95, "birthdate": 0.95 },
  "assuranceLevel": "DOCUMENT_SIGNED_QR",
  "livenessEvidence": "NONE",
  "provider": "EMNET_DOCUMENT",
  "source": "DOCUMENT_UPLOAD",
  "environment": "LIVE"
}
  • identityKeyType is DOCUMENT_PSEUDONYM on this lane and FIN_PSEUDONYM on the registry lane. The same person has two different keys on the two lanes: key your records by (identityKeyType, identityKey) and never join across types. A document pseudonym changes when the document is renewed. An older service may not send the field — never assume a type.
  • claimConfidence gives, per released claim read from the document, the extraction confidence in [0, 1]. It is evidence about extraction, never a decision: 1.0 does not mean “verified”.
  • New claims on this lane (released only when your account is entitled to them): document_type, document_issuing_country (ISO 3166-1 alpha-2), document_number_last4, document_issue_date, name_am (Ethiopic script), nationality_self_declared ("true" when the card printed a self-declared nationality) and gender as read from the document. Never released on any lane: the full document number (only its last four characters), the national ID number (individual_id) and the photo (picture). Your code must refuse a result that carries any of the three, whatever their letter case or surrounding spaces.
  • livenessEvidence is always NONE: the document lane does no liveness check.
  • The consumer rule still binds: under your production profile, grant anything from a result only if environment is exactly "LIVE". The decision is yours; Emnet returns claims, not decisions.

Next