Skip to content

API

API reference

Every public endpoint: parameters, example requests and responses, and the errors worth handling.

Base URL https://kycverify.me/api. JSON in and out (multipart for images), the x-api-key header on every call, and one error envelope. Timestamps are RFC 3339 UTC with second precision.

Endpoints
MethodPathPurpose
POST/v1/sessionsCreate a session and its hosted link
GET/v1/sessionsList sessions
GET/v1/sessions/{id}Session summary
GET/v1/sessions/{id}/decisionIdentity, checks and review
PATCH/v1/sessions/{id}/statusDecide a session in review
DELETE/v1/sessions/{id}Purge files, embeddings and identity
GET/v1/workflowsThe app's workflows
POST/v1/checks/amlScreen a name
POST/v1/checks/panValidate a PAN
POST/v1/checks/mrzParse an MRZ
POST/v1/checks/face-matchCompare two faces
POST/v1/checks/aadhaar/secure-qrVerify an Aadhaar Secure QR
GET/healthLiveness: {"ok": true} (no key)

Sessions

Create a session

POST/v1/sessions

Creates a verification session on the key's app and returns the hosted-flow link. The session runs a snapshot of the chosen workflow, so later workflow edits never change it.

Auth: x-api-key

Body

workflow_idstring · optional
A live workflow of this app. Omitted: the app's default workflow, else its oldest live one.
vendor_datastring ≤ 256 chars · optional
Your reference for the person. Echoed in every webhook and filterable in GET /v1/sessions.
callback_urlabsolute http(s) URL ≤ 2048 · optional
Where the hosted flow sends the person when it finishes, with session_id and status appended. Overrides the workflow's redirect_url.
metadataobject ≤ 8 KB · optional
Any JSON object. Returned with the decision.
contact.emailstring · optional
Pre-fills the email step. Must be a valid address.
expires_in_hoursinteger 1-720 · optional
Link lifetime. Default 168 (7 days).
Request · bash
curl -X POST https://kycverify.me/api/v1/sessions \
  -H "x-api-key: $KYC_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "vendor_data": "user-42", "expires_in_hours": 48 }'
Response · 201 Created · json
{
  "session_id": "ses_0k3v9x2m4a7qhd8f1rtb",
  "status": "not_started",
  "url": "https://kycverify.me/verify/q3Xf…",
  "session_token": "q3Xf…",
  "workflow_id": "wf_0k3t1c8n5e2wpzr6g4ya",
  "vendor_data": "user-42",
  "expires_at": "2026-10-10T09:12:44Z"
}
  • 400 bad_request: a field fails validation, e.g. expires_in_hours must be between 1 and 720.
  • 404 not_found: workflow_id is unknown, archived or belongs to another app.
  • 422 unprocessable: the app has no workflow yet.

List sessions

GET/v1/sessions

The key's sessions, newest first.

Auth: x-api-key

Query parameters

statusstring · optional
One of not_started, in_progress, processing, approved, declined, in_review, expired, abandoned.
vendor_datastring · optional
Exact match on your reference.
pageinteger ≥ 1 · optional
Default 1.
page_sizeinteger 1-100 · optional
Default 25.
Request · bash
curl "https://kycverify.me/api/v1/sessions?status=in_review&page_size=50" \
  -H "x-api-key: $KYC_API_KEY"
Response · 200 OK · json
{ "data": [ { "session_id": "ses_0k3v9x2m4a7qhd8f1rtb", "status": "in_review", "decision_reason": "aml_potential_match", "workflow_id": "wf_0k3t1c8n5e2wpzr6g4ya", "vendor_data": "user-42", "created_via": "api", "created_at": "2026-10-03T09:02:11Z", "updated_at": "2026-10-03T09:12:44Z", "started_at": "2026-10-03T09:03:20Z", "submitted_at": "2026-10-03T09:11:58Z", "completed_at": "2026-10-03T09:12:44Z", "expires_at": "2026-10-10T09:02:11Z", "purged_at": null } ], "page": 1, "page_size": 50, "total": 1 }
  • 400 bad_request: Unknown status ….

Get a session

GET/v1/sessions/{id}

The session summary: status, reason, timestamps. Use the decision endpoint for identity and checks.

Auth: x-api-key

Request · bash
curl https://kycverify.me/api/v1/sessions/ses_0k3v9x2m4a7qhd8f1rtb \
  -H "x-api-key: $KYC_API_KEY"
Response · 200 OK · json
{
  "session_id": "ses_0k3v9x2m4a7qhd8f1rtb",
  "status": "in_review",
  "decision_reason": "aml_potential_match",
  "workflow_id": "wf_0k3t1c8n5e2wpzr6g4ya",
  "vendor_data": "user-42",
  "created_via": "api",
  "created_at": "2026-10-03T09:02:11Z",
  "updated_at": "2026-10-03T09:12:44Z",
  "started_at": "2026-10-03T09:03:20Z",
  "submitted_at": "2026-10-03T09:11:58Z",
  "completed_at": "2026-10-03T09:12:44Z",
  "expires_at": "2026-10-10T09:02:11Z",
  "purged_at": null
}
  • 404 not_found: no such session in this app.

Get the decision

GET/v1/sessions/{id}/decision

The full decision: identity with a source per field, every check with its score, data and warnings, and the review if a person decided. Available at any status; checks fill in as the session progresses.

Auth: x-api-key

Request · bash
curl https://kycverify.me/api/v1/sessions/ses_0k3v9x2m4a7qhd8f1rtb/decision \
  -H "x-api-key: $KYC_API_KEY"
Response · 200 OK · json
{
  "session_id": "ses_0k3v9x2m4a7qhd8f1rtb",
  "status": "approved",
  "decision_reason": null,
  "workflow_id": "wf_0k3t1c8n5e2wpzr6g4ya",
  "vendor_data": "user-42",
  "metadata": {},
  "identity": {
    "full_name": "ANNA MARIA ERIKSSON",
    "date_of_birth": "1974-08-12",
    "nationality": "UTO",
    "document_type": "passport",
    "document_number_masked": "L8989****",
    "expiry_date": "2032-04-15",
    "sources": { "full_name": "mrz", "date_of_birth": "mrz" }
  },
  "checks": [
    { "kind": "document",   "status": "passed", "score": 0.97 },
    { "kind": "liveness",   "status": "passed", "score": 0.91 },
    { "kind": "face_match", "status": "passed", "score": 0.612 },
    { "kind": "aml",        "status": "passed", "score": 0 }
  ],
  "review": null,
  "created_at": "2026-10-03T09:02:11Z",
  "submitted_at": "2026-10-03T09:11:58Z",
  "completed_at": "2026-10-03T09:12:44Z",
  "expires_at": "2026-10-10T09:02:11Z"
}

Decide a session in review

PATCH/v1/sessions/{id}/status

Approve or decline a session that is in_review, from your own tooling. The API key is recorded as the actor and a webhook is sent.

Auth: x-api-key

Body

status"approved" | "declined" · required
The decision. A decline sets decision_reason to manual_review_declined.
notestring ≤ 2000 chars · optional
Kept with the review.
Request · bash
curl -X PATCH https://kycverify.me/api/v1/sessions/ses_0k3v9x2m4a7qhd8f1rtb/status \
  -H "x-api-key: $KYC_API_KEY" -H "content-type: application/json" \
  -d '{ "status": "approved", "note": "Portrait checked by hand" }'
Response · 200 OK · json
{ "session_id": "ses_0k3v9x2m4a7qhd8f1rtb", "status": "approved", "review": { "reviewed_by": null, "reviewed_at": "2026-10-03T10:40:02Z", "note": "Portrait checked by hand" }, … }
  • 409 conflict: the session is not in_review (the message names its status), or it changed while you decided.

Purge a session

DELETE/v1/sessions/{id}

Deletes the session's encrypted files, face embeddings, document fingerprints and extracted identity, strips the Decision from stored webhook deliveries, and audits the purge. An open session is ended as expired (reason purged); a session still processing is refused with 409 conflict. The row stays as a tombstone with purged_at set, so references and history stay consistent.

Auth: x-api-key

Request · bash
curl -X DELETE https://kycverify.me/api/v1/sessions/ses_0k3v9x2m4a7qhd8f1rtb \
  -H "x-api-key: $KYC_API_KEY"
Response · 204 No Content · json
(empty body)
  • 409 conflict: the session is processing; purge it once the engine has reached a decision.

Workflows

List workflows

GET/v1/workflows

The app's live workflows with their configuration, default first.

Auth: x-api-key

Request · bash
curl https://kycverify.me/api/v1/workflows -H "x-api-key: $KYC_API_KEY"
Response · 200 OK · json
{
  "data": [
    { "id": "wf_0k3t1c8n5e2wpzr6g4ya", "name": "Default", "is_default": true, "version": 3,
      "created_at": "…", "updated_at": "…", "config": { "steps": { … }, "decision": { "auto_decline": true }, "redirect_url": null } }
  ],
  "page": 1, "page_size": 1, "total": 1
}

Standalone checks

Run one check from your backend without a hosted flow. These calls create no session and store nothing.

Screen a name

POST/v1/checks/aml

Screens one name against every loaded sanctions list without a session. Matches are returned from a name score of 0.80, up to 10, best first. (In a session, the AML step instead applies the workflow's review_threshold, 0.82 by default.)

Auth: x-api-key

Body

full_namestring ≤ 300 chars · required
Any order of names; matching is order-insensitive.
date_of_birthYYYY-MM-DD | YYYY · optional
Adjusts scores: a match adds 0.05, a mismatch subtracts 0.10.
nationalityISO alpha-3 · optional
Adds nationality_match to each result where the entry lists nationalities.
Request · bash
curl -X POST https://kycverify.me/api/v1/checks/aml \
  -H "x-api-key: $KYC_API_KEY" -H "content-type: application/json" \
  -d '{ "full_name": "Anna Maria Eriksson", "date_of_birth": "1974-08-12" }'
Response · 200 OK · json
{
  "matches": [],
  "screened_at": "2026-10-03T09:12:40Z",
  "lists": [
    { "list": "ofac_sdn", "version": "3f9a1c0b7e2d", "fetched_at": "…", "entity_count": …, "status": "ok" },
    { "list": "un_consolidated", "version": "…", "fetched_at": "…", "entity_count": …, "status": "ok" }
  ],
  "warnings": []
}
  • If no list is loaded, matches is empty and warnings holds lists_not_loaded with severity high: nothing was screened.

Validate a PAN

POST/v1/checks/pan

Structural validation: format, holder type and, with a name, the name initial. There is no Income Tax Department lookup, and the response says so.

Auth: x-api-key

Body

panstring · required
Spaces are removed and letters upper-cased.
namestring ≤ 200 chars · optional
The holder's name, to compare the fifth character with the surname (individuals) or entity name.
Request · bash
curl -X POST https://kycverify.me/api/v1/checks/pan \
  -H "x-api-key: $KYC_API_KEY" -H "content-type: application/json" \
  -d '{ "pan": "ABCPE1234F", "name": "Ravi Kumar Edathil" }'
Response · 200 OK · json
{
  "normalized": "ABCPE1234F",
  "valid_format": true,
  "holder_type": "individual",
  "name_initial_match": true,
  "issues": [],
  "government_lookup": false,
  "note": "…structural validation only…"
}

Parse an MRZ

POST/v1/checks/mrz

Parses two or three MRZ lines (or up to 20 noisy OCR lines, from which it locates the MRZ) and recomputes every check digit. Supports TD1, TD2, TD3, MRV-A, MRV-B and the French national ID.

Auth: x-api-key

Body

linesstring[] (≤ 20, ≤ 200 chars each) · required
The MRZ lines in order, or OCR output containing them.
Request · bash
curl -X POST https://kycverify.me/api/v1/checks/mrz \
  -H "x-api-key: $KYC_API_KEY" -H "content-type: application/json" \
  -d '{ "lines": [
    "P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<",
    "L898902C36UTO7408122F1204159ZE184226B<<<<<10"
  ] }'
Response · 200 OK · json
{
  "format": "td3", "document_code": "P", "issuing_state": "UTO",
  "surname": "ERIKSSON", "given_names": "ANNA MARIA",
  "document_number": "L898902C3", "nationality": "UTO",
  "birth_date_raw": "740812", "birth_date": "1974-08-12", "sex": "F",
  "expiry_date_raw": "120415", "expiry_date": "2012-04-15",
  "optional_data": "ZE184226B", "optional_data_2": null,
  "check_digits": { "document_number": true, "birth_date": true, "expiry_date": true, "optional_data": true, "composite": true },
  "valid": true,
  "lines": [ "P<UTOERIKSSON<<…", "L898902C36UTO…" ],
  "corrections": []
}
  • 422 unprocessable: Could not parse an MRZ: no MRZ found (or an unsupported layout).

Compare two faces

POST/v1/checks/face-match

1:1 comparison of the most prominent face in each image, using SFace embeddings and cosine similarity. match is true at or above SFace's published threshold, 0.363.

Auth: x-api-key

Multipart fields

image_aJPEG, PNG or WebP ≤ 10 MB · required
For example the document portrait.
image_bJPEG, PNG or WebP ≤ 10 MB · required
For example a selfie.
Request · bash
curl -X POST https://kycverify.me/api/v1/checks/face-match \
  -H "x-api-key: $KYC_API_KEY" \
  -F image_a=@portrait.jpg -F image_b=@selfie.jpg
Response · 200 OK · json
{ "similarity": 0.612, "match": true, "threshold": 0.363, "faces": { "a": 1, "b": 1 } }
  • 422 unprocessable: no face found in one image, or the face models are unavailable.

Verify an Aadhaar Secure QR

POST/v1/checks/aadhaar/secure-qr

Decodes the text of an Aadhaar Secure QR and verifies UIDAI's RSA signature against the installed certificates. Returns the last four digits and demographics; never photo bytes, never the reference id.

Auth: x-api-key

Body

qr_textstring ≤ 16 KB · required
The QR payload: one long decimal number.
Request · bash
curl -X POST https://kycverify.me/api/v1/checks/aadhaar/secure-qr \
  -H "x-api-key: $KYC_API_KEY" -H "content-type: application/json" \
  -d '{ "qr_text": "6979414848205548481619299442879901900893978332594614407…" }'
Response · 200 OK · json
{
  "source": "secure_qr", "version": "V2",
  "aadhaar_last4": "1234", "generated_at": "2025-01-01T12:00:00Z",
  "name": "…", "dob_raw": "01-01-1990", "date_of_birth": "1990-01-01", "gender": "F",
  "address": { "district": "…", "state": "…", "pincode": "…", … },
  "address_text": "…",
  "mobile_hash": "…", "email_hash": null,
  "photo_format": "jp2", "photo_present": true,
  "signature": "valid", "signature_key": "uidai_offline_publickey_26022021.cer"
}
  • 422 unprocessable: Not a valid Aadhaar Secure QR: ….
  • signature is invalid or no_certificates when it cannot be verified; the response still decodes.