Concepts

Flows and steps

A flow decides what an applicant has to do. This page catalogues every step type with its configuration, what the applicant sees and what data and flags it produces, then explains flow versions, flags and severities, and review policies.

Flows

A flow is an ordered list of steps plus the legal context of the verification. You choose a flow per request with flowKey, or omit it to use the tenant default. Every new workspace gets three published templates you can use immediately or clone and edit in the portal flow builder:

flowKeyNameStepsReview policy
india-business-kyc-v1India Business KYCmobileOtp, emailOtp (optional), aadhaar, pan, bank, selfie, shopInside (optional), shopOutside (optional), documents (optional), business (optional), consentMANUAL
employee-verification-v1Employee VerificationmobileOtp, pan, aadhaar, selfie, consentHYBRID
bank-account-check-v1Bank Account CheckmobileOtp, bank, consentAUTO

List what is live for your workspace:

curl -s "https://kyc.example.com/v1/flows" -H "Authorization: Bearer $VERIFYME_API_KEY"

An unknown or unpublished flowKey returns 422 FLOW_NOT_FOUND.

Flow definition

{
  "purposeCode": "KYC_ONBOARDING",
  "noticeVersion": "2026-10-01",
  "retentionClass": "KYC_RESULT",
  "reviewPolicy": "HYBRID",
  "locale": "en",
  "steps": [
    { "key": "mobileOtp", "type": "MobileOtp", "sequence": 1, "required": true,
      "purposeCode": "IDENTITY_VERIFICATION", "config": { "expirySeconds": 300, "maxAttempts": 3 } },
    { "key": "pan", "type": "PanVerification", "sequence": 2, "required": true,
      "purposeCode": "IDENTITY_VERIFICATION", "providerBinding": "pan-default" },
    { "key": "consent", "type": "Consent", "sequence": 3, "required": true, "purposeCode": "KYC_ONBOARDING" }
  ]
}

Validation rules enforced when a flow is published:

  • purposeCode (uppercase letters and underscores, 3 to 60) and noticeVersion are required (DPDP purpose limitation and notice).
  • At most 25 steps. Step key is alphanumeric, starts with a letter, 2 to 41 characters and unique. sequence is an integer.
  • Every step that collects restricted data needs its own purposeCode (data minimisation).
  • A flow with restricted data must include a Consent step.
  • Face match threshold must be an integer from 30 to 99.
  • reviewPolicy is MANUAL, AUTO or HYBRID.

Flow versions

Published versions are immutable. Editing a flow creates a draft (a new version number); publishing it makes it the live version for new requests. A request is pinned to the version that was live when it was created, so changing a flow never alters in-flight applicants. The reviewer and the result API always interpret a request against its own version. Roll forward by publishing again; to stop using a flow, remove it as the default or from your code.

Step catalogue

Steps that need an external provider (SMS, email, DigiLocker, PAN, bank, face, liveness, pincode) use the provider bound to your workspace. The whole journey is charged once, at the journey price, when the applicant starts. Each step below lists the API that covers its provider call; the first attempt per step is included in the journey price, and every further attempt is charged at that API price. The sandbox is free.

Step typeProvider capabilityAPI (extra attempts)
MobileOtpsmsnone
EmailOtpemailnone
PersonalDetailsnonenone
Addresspincodenone
DigiLockerAadhaardigilockerAADHAAR_VERIFICATION
PanVerificationpanPAN_VERIFICATION
BankVerificationbankBANK_VERIFICATION
SelfieFaceMatchfaceFACE_MATCH
LivenesslivenessVIDEO_LIVENESS
GeoPhotoInside, GeoPhotoOutsidenoneDOCUMENT_PROCESSING
DocumentUploadnoneDOCUMENT_PROCESSING
BusinessDetailspincodenone
Consentnonenone

There is no separate charge per request or per submission: the journey price covers them. Failures on the provider's side (timeouts and outages) are never charged. Provider calls are cached for ten minutes per identical input so a retry is not billed twice. A step that succeeds is locked: the applicant cannot go back and redo it.

MobileOtp

Config
channel (sms), expirySeconds (300), maxAttempts (3)
Applicant sees
Mobile number field (prefilled and locked if you passed mobile), then a six-digit code input with resend timer (30 seconds)
Limits
5 sends per hour per step, 10 per hour per number per workspace, one active code per step
Result
verified.mobile (masked, for example 98XXXXXX34); step data holds the masked target
Errors
INVALID_MOBILE, INVALID_OTP, OTP_EXPIRED, OTP_ATTEMPTS_EXCEEDED
Flags
PROVIDER_UNAVAILABLE if SMS delivery times out (delivery is retried in the background)

EmailOtp

Config
expirySeconds (600), maxAttempts (3)
Applicant sees
Email field (prefilled and locked if you passed email) and code input
Result
verified.email (masked)

PersonalDetails

Config
minAge (18), fields (fullName, dob, gender)
Applicant sees
Name, date of birth, gender (M, F, O)
Result
verified.fullName, verified.dateOfBirth, verified.gender
Errors
INVALID_NAME, INVALID_DATE, UNDERAGE

Address

Config
none
Applicant sees
Address lines, pincode with automatic city and state lookup, city, state
Result
verified.address
Errors
INVALID_PINCODE

DigiLockerAadhaar

Config
none
Applicant sees
A button that opens the DigiLocker consent page (a simulator in the sandbox), then returns and shows progress while VerifyMe polls the provider
Result
verified.fullName, verified.dateOfBirth, verified.gender, verified.address and masked verified.aadhaar (last four digits only). The full Aadhaar number is never stored or returned
Events
verification.pending then verification.completed
Errors
DIGILOCKER_NOT_COMPLETED (denied or abandoned), PROVIDER_UNAVAILABLE

PanVerification

Config
requireNameMatch (true), nameMatchMin (70), nameMismatch (block or flag, default block)
Applicant sees
PAN field (uppercase, format ABCDE1234F). The name to match comes from DigiLocker, then Personal details, then an optional entered name
Limits
10 attempts per hour per request
Result
verified.pan (masked, ABXXXXXX4F); step data has category (Individual, Company, Firm, HUF, Trust, AOP)
Flags
By default a name mismatch stops the step (retry, then PAN_VERIFICATION_FAILED). With nameMismatch: flag the applicant continues and PAN_AADHAAR_MISMATCH (HIGH) if the PAN holder differs from the DigiLocker name (similarity below 70), else PAN_NAME_MISMATCH (MEDIUM) against the entered name
Errors
INVALID_PAN, PAN_INVALID, PROVIDER_UNAVAILABLE (attempt not consumed)

BankVerification

Config
nameMatchMin (70), nameMismatch (block or flag, default block)
Applicant sees
Account number (9 to 18 digits), IFSC, optional holder name
Limits
8 attempts per hour per request
Result
verified.bankAccount (masked, last four digits), verified.bankAccountHolder
Name check
The account holder must match the Aadhaar name or the PAN name (one is enough). Matching ignores word order, accepts initials, spelling variants and a missing middle name. A mismatch asks the applicant to retry; after the allowed attempts the step fails with BANK_VERIFICATION_FAILED and the applicant is told to contact the business. With nameMismatch: flag the applicant continues and a BANK_NAME_MISMATCH flag is raised for review
Errors
INVALID_ACCOUNT, INVALID_IFSC, BANK_ACCOUNT_INVALID, PROVIDER_UNAVAILABLE

SelfieFaceMatch

Config
threshold (60, allowed 30 to 99), maxAttempts (3). Advanced: onFail: "block" fails the step permanently instead of sending to review; providerFallback: "autopass" completes the step with flag AUTOPASS_PROVIDER_FAILURE (HIGH) if the face provider is down
Applicant sees
Camera capture (or file picker) with guidance, retake on mismatch with attempts left
Result
verified.faceMatchScore, verified.faceMatchPassed; a SELFIE document
Flags
FACE_BELOW_THRESHOLD (HIGH) when attempts are exhausted. The applicant is told the selfie will be reviewed manually
Errors
FACE_MISMATCH (retry), FACE_VERIFICATION_FAILED (when blocked), FILE_TYPE_NOT_ALLOWED, FILE_TOO_LARGE

Liveness

Config
maxAttempts (3)
Applicant sees
Short guided capture of 3 to 10 frames
Result
verified.livenessPassed; a LIVENESS_FRAME document
Flags
LIVENESS_FAILED (HIGH) on failure (retryable)

GeoPhotoInside and GeoPhotoOutside

Config
requireGeo (false)
Applicant sees
Camera capture for the premises with a location permission prompt
Result
A document typed PREMISES_INSIDE or PREMISES_OUTSIDE with latitude, longitude and accuracy visible to reviewers. Not part of the verified object
Flags
GEO_UNAVAILABLE (LOW) if no location was captured and requireGeo is false. With requireGeo: true the photo is refused with GEO_REQUIRED

DocumentUpload

Config
documentTypes (default REGISTRATION_CERTIFICATE, ADDRESS_PROOF), maxFiles (3)
Applicant sees
Choose a document type, upload JPEG, PNG, WebP or PDF (up to 8 MB), then Done
Result
Entries in documents[] of the result (type, contentType, sizeBytes, scanStatus, uploadedAt) and a document.uploaded event each
Flags
DOCUMENT_SCAN_FAILED (HIGH) for files rejected by the malware scan
Errors
MAX_FILES_REACHED, DOCUMENT_REQUIRED, FILE_TYPE_NOT_ALLOWED

BusinessDetails

Config
fields (businessName, businessType, gstin, pincode, city, state)
Applicant sees
Business name, type (proprietorship, partnership, llp, private_limited, public_limited, other), optional GSTIN, location
Result
Recorded on the profile (GSTIN stored masked) and shown to reviewers. In 1.0.0 these fields are not returned in the API result object
Errors
INVALID_GSTIN, INVALID_PINCODE
Config
none (the notice is generated from the flow's purposeCode, step purposes and noticeVersion)
Applicant sees
A clear notice naming your business, each purpose, and an I agree control
Result
One consent record per purpose with notice version, text hash, time, IP, locale and source (hosted or SDK). Withdrawal is available to the applicant
Errors
CONSENT_REQUIRED

Flags and severities

A flag is a risk signal produced by a step. Flags never block an applicant by themselves; they inform automated and manual review. Flags are immutable per attempt, and the active set is whatever the latest attempt of each step produced, so a successful retry clears the earlier flag from the active set (history is kept for audit).

FlagSeverityRaised byMeaning
PAN_AADHAAR_MISMATCHHIGHPANPAN holder name differs from the DigiLocker name
FACE_BELOW_THRESHOLDHIGHSelfieFace score below threshold after all attempts
LIVENESS_FAILEDHIGHLivenessLiveness check failed
DOCUMENT_SCAN_FAILEDHIGHUploads, selfieFile rejected by malware scan
AUTOPASS_PROVIDER_FAILUREHIGHSelfieFace provider down and the flow allowed auto-pass. Never auto-approved
PAN_NAME_MISMATCHMEDIUMPANPAN holder name differs from the entered name
BANK_NAME_MISMATCHMEDIUMBankAccount holder differs from the expected name
GEO_UNAVAILABLELOWGeo photoNo location captured
PROVIDER_UNAVAILABLEINFOAny provider stepA provider timed out. Applicant could retry. Not a negative result

The result API exposes flags[] as { code, step, severity }. Flags without a mapping default to LOW.

Review policies

After the applicant submits, the request is reviewed. The effective policy is decided like this:

PolicyBehaviour
MANUALAlways goes to the review queue. Default for new tenants
AUTOApproved automatically when there is no HIGH active flag
HYBRIDApproved automatically only when there is no active flag except INFO; otherwise a human decides
  • Tenant policy is a ceiling. If the workspace policy is MANUAL, no flow is ever auto-approved, whatever the flow says. Set the workspace policy to AUTO or HYBRID to let flows choose.
  • A request with AUTOPASS_PROVIDER_FAILURE is never auto-approved.
  • Automatic approvals are recorded with reason code VERIFIED_OK, comment "Auto-approved by ... review policy", and data.automated: true in onboarding.approved. Rejection is never automatic.

Reviewer actions

Reviewers work in the portal. Each action is audited and emits a webhook:

ActionResultEvent
startRequest moves to REVIEWINGnone
approveAPPROVED. All required steps must be completeonboarding.approved
rejectREJECTEDonboarding.rejected
revert / request_infoREVERTED with the chosen steps set back for the applicant; link reopens, expiry extendedonboarding.reverted

Reason codes

Approve, reject and revert require a reason code: VERIFIED_OK, DOCUMENT_UNCLEAR, DATA_MISMATCH, FACE_MISMATCH, MISSING_INFORMATION, FRAUD_SUSPECTED, POLICY_VIOLATION, OTHER. A workspace can also require a free-text comment on every decision (requireReasonComment). Comments are internal and are not sent in webhooks, except the applicant-facing message of a revert / request for information.

Maker-checker

With maker-checker enabled for the workspace, a final approve or reject needs two different reviewers. The first reviewer's decision is recorded as *prepared* and the request shows review status AWAITING_CHECKER; the same user cannot confirm it. Only the second reviewer's confirmation changes the status and emits the event. Automated policy approvals are exempt.

Docs version 1.0.0API version v1Last updated 1 Oct 2026