Core concepts

Verifications

A verification represents one identity check for one end user. You create it, provide a document and selfie, and OKIAS returns a decision with a risk score and reason codes.

The lifecycle is simple: create → submit assets (or use the hosted flow) → OKIAS scores the signals → you read a final status.

Create a verification

POST/v1/verifications

Creating a verification places a hold on 15 credits (in live mode) and returns a hosted_url you can redirect the user to. Always send an Idempotency-Key header.

curl https://api.okias.io/v1/verifications \
  -H "Authorization: Bearer $OKIAS_API_KEY" \
  -H "Idempotency-Key: 9f2c4a1b-1e33-4c07-9c1e-2b7f0d5a1a10" \
  -H "Content-Type: application/json" \
  -d '{
    "level": ["FULL_KYC"],
    "country": "pk",
    "doc_type": "NATIONAL_ID",
    "end_user_ref": "user_8842",
    "applicant": {
      "expected_first_name": "Ayesha",
      "expected_last_name": "Khan",
      "expected_dob": "1994-03-22"
    }
  }'

Request body

FieldTypeDescription
levelstring[]The checks to run, e.g. ["FULL_KYC"]. The catalog has exactly three values: FULL_KYC, ID_DOCUMENT, AML_SCREENING (see below). Any other value is rejected with 400 VALIDATION_ERROR (UNKNOWN_CHECK:<value>). Optional — defaults to the key's configured checks.
countrystring (ISO-3166 alpha-2)Two-letter country of the user's document, e.g. pk. Optional; improves document routing and checksum validation.
doc_typeenumOne of PASSPORT, NATIONAL_ID, DRIVING_LICENCE, RESIDENCE_PERMIT. Optional — inferred from the document if omitted.
end_user_refstringYour own reference for the user (max 120 chars). Echoed back so you can reconcile the decision to your records. Optional.
applicantobjectThe identity you already hold for this person, cross-checked against the document.expected_first_name, expected_last_name, expected_dob — all optional; a field you omit is skipped rather than counted as a mismatch. Recommended.
addressobjectThe applicant's address, for projects that require one (project settings → required fields). permanent and/or current, each { line1, line2?, city, region?, postal_code?, country } (country is ISO-3166 alpha-2), plus current_same_as_permanent: true to copy the permanent address. Only the parts your project requires are stored (encrypted); anything else is discarded. If a required part is missing or incomplete the request fails with 400 ADDRESS_REQUIRED and a missing list. Optional — omit it and the hosted page asks the applicant instead.
assetsAsset[]Optionally include the document/selfie assets at creation to run in one call. Otherwise submit them separately (below) or via the hosted flow.
Send the expected identity from your side

If you already know who this person claims to be — from your own signup — pass it in applicant. We compare it to what we read off the document and flag a disagreement.

It belongs on your call rather than in the applicant’s form for two reasons. The applicant never sees it, so they cannot make it agree with whatever document they are holding — which is what makes it evidence at all. And it removes a form from the flow: the person just photographs their ID, and we read the name and date of birth off it.

The response is 201 Created with the verification as a flat JSON object — there is no data envelope. status starts at PENDING:

201 Created
{
  "id": "cms80k9ix001moea2jxy64fgu",
  "reference_code": "3J7Q9WD2M4XT",
  "status": "PENDING",
  "country": "pk",
  "doc_type": "NATIONAL_ID",
  "end_user_ref": "user_8842",
  "cost_credits": 15,
  "mode": "LIVE",
  "risk_score": null,
  "hosted_url": "https://okias.io/verify/cms80k9ix001moea2jxy64fgu",
  "liveness_challenge": null,
  "reason_codes": [],
  "created_at": "2026-07-30T10:00:00.054Z",
  "completed_at": null
}

Levels & checks

level is an array of check identifiers. The purchasable catalog is exactly three checks — anything else returns 400 VALIDATION_ERROR with UNKNOWN_CHECK:<value>. Your API key's scope determines which of them it may request; see key scopes.

CheckWhat it runsCredits
FULL_KYCThe complete pipeline: document authenticity and registry validation, liveness detection, 1:1 face match, and sanctions screening.15
ID_DOCUMENTDocument-only: classification, OCR/MRZ extraction, registry checks and tamper detection.see pricing
AML_SCREENINGSanctions/watchlist screening of the name read from the document.see pricing

FULL_KYC includes both the document check and sanctions screening. When you request ID_DOCUMENT and AML_SCREENING as separate checks instead, each is priced as its own item, at the credits listed for it by GET /v1/pricing, and cost_credits is the sum of the checks you requested. In live mode that total is the size of the hold, and it is still charged only if the verification resolves APPROVED.

Prefer FULL_KYC
FULL_KYC already includes the document check and sanctions screening and draws 15 credits. Compare it with the per-item credits in the table above before requesting the sub-checks separately. The pricing of these separate checks is under review.
KYB, proof of address, bank, phone and email checks are not available
KYB_BUSINESS, PROOF_OF_ADDRESS, BANK_STATEMENT, PHONE_VERIFICATION and EMAIL_VERIFICATION are not built and are no longer in the catalog — sending any of them returns 400 VALIDATION_ERROR / UNKNOWN_CHECK. If you integrated against an older version of this page, drop them from your level array.
15 credits per approved AI KYC
An AI KYC (full-KYC) verification costs 15 credits ($0.15). In live mode the credits are held when you create the verification and consumed only on APPROVED. DECLINED, expired and ERROR verifications release the hold — they are free. A REVIEW keeps the hold until a human reviewer resolves the case, and is charged only if it resolves to approved. Sandbox (ok_test_) never holds or charges.

Holds by product

The same hold-then-charge-on-approval rule applies to every KYC product:

ProductHeld at creationCharged
AI KYC15 creditsOnly if the final result is APPROVED
Manual KYC (decided by the OKIAS review team, or by you in merchant-decision mode)5 credits ($0.05)Only if the reviewer approves; released if declined or expired
BYOK — AI KYC on your own AI key (platform fee)5 credits ($0.05)Only if the final result is APPROVED

Every KYC product — AI KYC, Manual KYC (decided by OKIAS or by you) and BYOK — is charged only when the final result is APPROVED. Declined, expired and errored verifications are never charged, and reviews or action-required requests cost nothing until they end approved. Credits are reserved (held) while a verification is open and released automatically if it does not end approved.

Pricing API

GET/v1/pricing

The public price list (no authentication) is the source of every price on this site and of the cost_credits a verification is created with. A verification keeps the price it was created with. The response (api_version: 2) includes:

  • products[] — each product on sale with key, credit_cost, usd, billing_trigger (ON_APPROVAL for every KYC product) and includes[].
  • credit — the value of one credit in USD (fixed).
  • trial — the free trial credits granted after e-mail verification, or null.
  • topup — minimum and maximum top-up, volume bonus tiers and payment_methods[].
  • when_charged[] — the charging rule per outcome (approved, declined, review, expired, error, sandbox).
  • subscription and partner — white-label plans and partner payout terms.

The response carries an ETag; send it back in If-None-Match to receive 304 Not Modified when nothing changed. POST /v1/pricing/quote and GET /v1/pricing/convert are retired and answer 410 ENDPOINT_RETIRED: use GET /v1/pricing for list prices and POST /v1/me/billing/quote (signed-in console session) for the prices that apply to your own account.

Liveness on a verification

Liveness reason codes appear in the verification's reason_codes; they are listed under Errors and reason codes.

  • liveness_challenge is deprecated and is always null. No liveness instructions are published in a response before the capture.
  • Pure-API submissions (assets inline in the create call, or POST /v1/verifications/:id/submit) have no interactive capture; their liveness frames are assessed like a passive capture, and borderline cases lean to REVIEW.
How liveness is assessed today
Liveness is currently assessed passively in production: an AI model reviews the captured frames for presentation attacks. The prompted actions guide the capture, but the server does not yet verify that they were performed or in which order, so the challenge is not an anti-replay control today. Server-verified active liveness is in development; this page will say so when it is live.

Submit assets

POST/v1/verifications/:id/submit

If you run your own capture UI, submit the document and selfie as base64 (no data: prefix). Each asset has a kind, an optional mime_type and the data.

curl
curl https://api.okias.io/v1/verifications/cms80k9ix001moea2jxy64fgu/submit \
  -H "Authorization: Bearer $OKIAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "assets": [
      { "kind": "DOCUMENT_FRONT", "mime_type": "image/jpeg", "data": "<base64>" },
      { "kind": "DOCUMENT_BACK",  "mime_type": "image/jpeg", "data": "<base64>" },
      { "kind": "SELFIE",         "mime_type": "image/jpeg", "data": "<base64>" }
    ]
  }'

Asset kinds

kindDescription
DOCUMENT_FRONTFront of the ID document. Required.
DOCUMENT_BACKBack of the document, where applicable (e.g. national ID cards).
SELFIEA selfie for 1:1 face match against the document portrait.
LIVENESS_FRAMEFrame(s) captured during liveness. Supplied automatically by the hosted flow.
Prefer the hosted flow for capture
The hosted flow handles capture quality, document guidance and liveness detection for you — no need to build a camera pipeline or submit assets yourself.

Statuses

A verification's status moves from pending to one of four terminal outcomes:

statusMeaningWhat to do
PENDINGCreated, awaiting assets / still processing.Wait for the webhook or poll GET.
APPROVEDChecks passed. Identity verified.Provision the user.
REVIEWBorderline signals routed to manual review.Hold the user; a final event follows.
DECLINEDChecks failed (e.g. face mismatch, tampered document).Block or retry per your policy.
ERRORProcessing could not complete (e.g. unreadable capture).Ask the user to retry; the credit hold is released.

In lowercase, webhook events map to these outcomes: verification.approved, verification.review, verification.declined and verification.error, plus verification.expired when a session is not completed within the session lifetime (24 hours by default). See Webhooks.

Risk score & reason codes

Every resolved verification carries a risk_score and a reason_codes array explaining the decision.

  • risk_score is 0–100, and higher is worse. 0 means no risk detected; 100 is maximum risk. It is the weighted risk across the stages that ran, rounded to an integer.
  • UI band guidance: treat < 20 as low risk, 20–59 as elevated, and ≥ 60 as high risk.
  • APPROVED results carry a low score and an empty reason_codes array — approved verifications never carry reason codes. A declined one carries the specific signals that failed alongside a high score.
declined example (abridged)
{
  "id": "cms81hq0p0031oea2vv17b1om",
  "status": "DECLINED",
  "risk_score": 71,
  "reason_codes": ["FACE_MISMATCH"],
  "completed_at": "2026-07-30T10:04:11.900Z"
}

Common reason codes

Reason codeMeaning
FACE_MISMATCHThe selfie did not match the document portrait.
COUNTRY_NOT_SUPPORTEDThe document's country is not eligible for verification.
DOC_NUMBER_INVALIDThe document number failed format or checksum validation.
DOCUMENT_TAMPERINGStrong evidence of document manipulation (see TAMPER_* signals).
LIVENESS_SPOOFThe liveness check detected a presentation attack (also emitted as LIVENESS_SPOOF_<TYPE>).
SANCTIONS_POSSIBLE_MATCHA possible sanctions/watchlist match — routed to human review, never auto-declined.
PROMPT_INJECTION_SUSPECTEDInstruction-like text aimed at the verification model was found in the submitted media; the affected stage is not trusted and the case goes to human review.
RISK_SCORE_HIGHThe aggregate weighted risk crossed the decline threshold.
Reason codes are additive
A single decision can carry multiple reason codes. Treat them as diagnostic signals for your own risk policy and support tooling. The complete reference — every code the engine emits, with the recommended integrator action — is in Errors & rate limits → Reason codes.

Retrieve a verification

GET/v1/verifications/:id

Fetch the current state and decision at any time. On top of the fields returned by create, GET includes four owner-only detail fields. All four are null until processing completes (and stay null where no data was captured):

FieldDescription
extractedIdentity fields read from the document (e.g. fullName, documentNumber, dateOfBirth, dateOfExpiry).
imagesThe captured document/selfie images as data: URLs (front, back, selfie); subject to media retention.
declaredWhat the applicant typed in the hosted flow (firstName, lastName, dateOfBirth, dateOfExpiry, sex).
matchDeclared-vs-document cross-match verdict: status (MATCH / PARTIAL / MISMATCH) plus per-field verdicts.
addressThe applicant address (sent by you at creation or typed on the hosted page), present only when your project requires one.
200 OK
{
  "id": "cms80k9ix001moea2jxy64fgu",
  "reference_code": "3J7Q9WD2M4XT",
  "status": "APPROVED",
  "country": "pk",
  "doc_type": "NATIONAL_ID",
  "end_user_ref": "user_8842",
  "cost_credits": 15,
  "mode": "LIVE",
  "risk_score": 8,
  "hosted_url": "https://okias.io/verify/cms80k9ix001moea2jxy64fgu",
  "liveness_challenge": null,
  "reason_codes": [],
  "created_at": "2026-07-30T10:00:00.054Z",
  "completed_at": "2026-07-30T10:03:41.120Z",
  "extracted": { "fullName": "OKIAS SAMPLE USER", "documentNumber": "AB1234567", "dateOfBirth": "1990-01-01", "dateOfExpiry": "2030-01-01" },
  "images": { "front": "data:image/jpeg;base64,...", "back": null, "selfie": "data:image/jpeg;base64,..." },
  "declared": { "firstName": "Okias", "lastName": "Sample User", "dateOfBirth": "1990-01-01" },
  "match": { "status": "MATCH", "fields": { "name": "MATCH", "dateOfBirth": "MATCH", "dateOfExpiry": "SKIP", "sex": "SKIP" }, "reasons": [] }
}

Reference code

GET/v1/verifications/by-reference/:code

Every verification also carries a reference_code: twelve uppercase characters (digits and letters, never I, L, O or U, so nothing is ambiguous when it is read aloud or retyped). Print it on receipts, put it in confirmation e-mails, quote it in support. It is randomly generated server-side and contains no personal data, no timestamp and no account identifier, so it is safe to log and to show to your own users.

  • Permanent and unique. A code is assigned once, never changes, and is never reused — even if the verification is deleted.
  • Forgiving on input. Case, spaces and hyphens are ignored, and because I, L and O never appear in a code, typing one is read as 1, 1 and 0. So 3j7q-9wd2-m4xt and 3J7Q 9WD2 M4XT both resolve.
  • An identifier, not a secret. This endpoint still requires your API key and only ever returns your own verifications. An unknown code, a malformed code and another merchant's code all return the same 404 NOT_FOUND.
  • Verifications created before this field existed omit it. Keep storing the id as your join key.

Idempotency

Send a unique Idempotency-Key header on every create (supported on POST /v1/verifications and the partner variant POST /v1/p/:code/verifications). If a network hiccup makes you retry, OKIAS returns the original verification instead of creating a second one — so retries never double-charge or duplicate.

  • The header is optional — but omitting it makes every call chargeable. Without an Idempotency-Key, each request creates a brand-new verification (and, in live mode, a new credit hold).
  • 24-hour replay window. Repeating the same key within 24 hours returns the same verification object — never a second charge.
  • Concurrent duplicates can return 409. If two requests race on the same key and the first has not finished, the duplicate may fail with 409 IDEMPOTENCY_IN_PROGRESS — retry it and you will get the original verification.
  • Use a UUID or any unique string per logical operation (e.g. per user signup attempt).
  • Reuse the same key when retrying the same request; use a new key for a genuinely new verification.

Next steps