> ## Documentation Index
> Fetch the complete documentation index at: https://docs.privue.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Step types

> The sixteen step types a workflow can run, and what each one makes possible.

Every workflow is assembled from the step types on this page. Which of them your journey runs, in what
order and with which settings, is configured with us during onboarding - so read this as the menu:
what each type asks of the user, what it checks, and what it gives the rest of the journey.
Whatever the mix, every step reports the same envelope; [Reading the result](/verification/results)
is the field-by-field reference for reading one. The value in backticks under each name is the `type`
the API reports for that step.

To see the mix configured for you rather than the menu, read the workflow itself with
`GET /workflows/{workflow_key}`. It returns the workflow's `mode` - whether the person walks a journey
themselves or you supply everything over the API - and every step it can present, in the order they
are answered, each with the condition that gates it and an `intake` saying how it is answered. Where a
journey is presented in named parts, it also returns `groups` - the headings, in the order they are met -
and each step that belongs to one names it in `group`.

<Warning>
  A workflow's `mode`, its step keys and each step's `intake` are stable. The rest is a reference view
  of configuration we hold and maintain: which steps a workflow declares, how each is configured under
  `settings`, and the conditions that gate them all change without notice as your journey is tuned,
  each change a new version that only the verifications created after it run. A
  step's slots reach you twice: `intake` names the keys and the shapes you address a document by, and
  `settings` words the same slots as the person providing them is asked for them. Address a document
  by what `intake` says. Read the workflow to understand a journey; the stable thing to integrate with
  is the `steps` array on a verification record, described in [Reading the result](/verification/results).
</Warning>

On a staged workflow, a step is answered from what you upload to its slots and send for its
values. Three types are answered by the person being verified rather than by you: `selfie` and
`premises-photos` are captured live through a camera, and `digilocker` is a consent granted on the
issuer's own screens. Only a hosted workflow declares one of those, and `review` likewise, so a
staged workflow you read never contains them.

Four properties do the composing:

* **Required or optional.** The user may decline an optional step, and the record states that
  they did.
* **Conditional.** A step can be included only for the users it concerns: extra proof only when a
  check did not pass, a second account only when your `context` asks for one.
* **Grouped.** Consecutive steps can be presented under one heading - the several that together
  establish registration details, say - so a long journey reads as a few named parts rather than one
  queue.
* **Fed by earlier steps.** A step can read what an earlier one established - the names a GST
  registration is held under can be the names the bank account must be registered in. This is how the
  types below combine into one coherent journey.

## Form

`form`

Typed fields you define: text, number, date, yes/no, a choice from options you list, or a postal
address. A text field can be held to a format - PAN, GSTIN, IFSC, mobile, email, or PIN code - and a
field can be pre-filled from a value established earlier, with the user confirming or correcting it. A
value held to a format is read with surrounding spaces dropped, and a PAN, GSTIN or IFSC in any case,
so `abcpe1234f` is accepted and published as `ABCPE1234F`. An optional field sent empty, or as
nothing but spaces, is a field left blank. A number sent for a text field is read as the text a person
would type for it, so `411001` or `411001.0` sent for a PIN code is the PIN code `"411001"`.

An `address` field asks for an address in India in its parts and publishes it as one object, in the
shape a GST certificate states a place of business in: `street`, `city`, `state`, `country` and
`postal_code`. Every part has to be given, `state` is one of the states and union territories the API
reference lists, `postal_code` is a six-digit PIN code, and `country` is `India`. Plumb a GST
certificate's `primary_business_address` into one to pre-fill it with the registered place, or another
form's address field to pre-fill it with what the user gave there.

A field can be asked on some journeys and not others. Each gate on it names an earlier field of the
same form, or a value the form is given, together with the values of that key which ask for this
field; a field carrying several gates is asked when every one of them holds. A field the journey
never asked publishes nothing, exactly as an optional field left blank does.

Fields that can never both be asked may publish under one name, which is how a single name carries
whichever of them the journey reached. Ask a company for the registration details a company has and
an individual for theirs, and read one value back whichever way the journey went.

A form waits for the step supplying a value it reads to settle, and asks the rest of its fields
whether or not that value arrived: a check that establishes nothing costs the applicant only the
fields that read it. A form the journey leaves nothing to ask is not part of that journey, the same
way a step whose condition did not hold is not.

The answers are published under your field names, for later steps and for your own systems. Only a
hosted workflow asks a form: it writes down what the person tells you and establishes nothing about
it, so a workflow you supply the values for sends them as context instead.

## Documents

`documents`

Files gathered into named slots you define. Each slot names the kinds of document that satisfy it, and
each kind says how it arrives: PDFs, images or either, one file or up to a count. The step checks
presence and file type and nothing more: use it to collect paperwork your own reviewers will read.

Only a hosted workflow gathers documents this way. It establishes nothing about what the files hold,
so a workflow you supply the files for has nothing to learn from one; attach those files to the checks
that read them instead.

## Parsed documents

`parsed-documents`

Several documents in one step, each read and held to what your workflow says about it. A slot names
the kind of document that satisfies it, or a choice of kinds where either would do, and whoever
provides the files says which they sent - so each document is read as what it is, and one sent as
something else comes back unreadable rather than misread.

What each kind is held to is configured for you. A date the document runs to can be required to be
still in force, and to report itself when it is close to running out; a name it is printed in can be
scored against a name the step is given; and an identifier it carries can be required to be exactly
one the step is given. A check that fails can either fail the step or be reported on a step that
still passes, whichever the requirement warrants. Where the journey holds nothing to compare against,
or the document prints no such value, the step publishes the check as one that could not be made
rather than as one that passed.

A name or an identifier a check reads comes from wherever your workflow has it: a value an earlier
step established, a field of the context you supplied when creating the verification, or one written
into the workflow. A check names the value it held the document to, so a reading carries what the
document had to say beside what it said.

Publishes every document as it was read, each with the checks it was held to and how each came out.
Expiry is judged on the day the step is judged, so a registration that lapses between one reading and
the next has lapsed.

## PAN card

`pan-card`

The user provides a clear photo or PDF scan of a PAN card. The card is read and checked against
the PAN registry, and the workflow can additionally hold it to a specific PAN, to the PAN behind a
GSTIN established earlier, or to a roster of named people - so the card is not just genuine but the
right person's. Publishes the PAN, the holder's name, the date of birth or incorporation the card
prints, and the card image, which a selfie step can match a face against, along with the registry's
own word on the PAN: whether it is operative, and whether an Aadhaar is seeded against it, which
only a PAN held by a person can be.

## GST certificate

`gst-certificate`

The user uploads their GST registration certificate, as one PDF or a photo per page. The
certificate is read and confirmed against the GST registry, and passes when the registry holds the
registration as active. One still active pending verification passes as well, and carries the gap
`gst-pending-verification`, since the GST department has not finished verifying it.
Publishes the registered identity - GSTIN, legal and trade names, registered addresses, business
constitution, the promoters the certificate names, and the business PAN - which is the material most
other checks are held against.

It also publishes the rest of what the registration states about the business: its taxpayer type,
whether e-invoicing is mandated for it, the turnover slab it is registered in, its authorised
signatories, the activities it is registered to carry on, the HSN and SAC codes covering the goods and
services it deals in, and its return filing history - every GST return the registry holds, of whatever
type and period, each with the status and filing date the registry states.

Where your workflow asks for it, the step also reads the contact registered against the GSTIN and
publishes it as `business_email` and `business_mobile`. A step that does not ask carries `null` in
both, as does one that asks against a registration holding no contact.

Where you already know which registration you want a certificate for, the step can be given the GSTIN,
and a certificate for any other one fails it. It can be given a PAN instead, which asks for a
certificate of whichever registration that PAN holds, since a GSTIN carries its holder's PAN. Either is
read off the certificate itself, so a certificate for another business is answered as the wrong
document rather than checked against the registry.

Where your workflow has established what the business is registered as, the step can be given that
constitution and holds the registration to it. A certificate the registry holds under any other
constitution fails the step, however sound the registration behind it is. Give it one of the
constitutions the registry issues, spelled as the registry spells them: `Sole Proprietorship`,
`Partnership`, `Hindu Undivided Family`, `Private Limited Company`, `Public Limited Company`,
`One Person Company`, `Limited Liability Partnership`, `Foreign Limited Liability Partnership`,
`Foreign Company`, `Unlimited Company`, `Public Sector Undertaking`, `Society/ Club/ Trust/ AOP`,
`Government Department`, `Local Authority`, `Statutory Body`, `Trust`, `Others`.

Whichever of the three the step is given is shown in the ask, so a business holding a registration in
each state it operates in is told which one to reach for rather than finding out by uploading the
wrong one.

A workflow whose subjects are not all GST registered can set `ask_if_registered`, and the step puts
that question before anything is uploaded. A business that is registered uploads its certificate as
usual, and one that is not declines the step, which the record states the way it states any decline.
A step that asks sets `required` to false, and later steps branch on it with a `step-outcome`
condition of `passed` or `declined`.

## Bank account

`bank-account`

The user types the account number and IFSC, or uploads a document evidencing the account. Which
documents count is per your workflow: any of a bank statement, a cancelled cheque and a bank letter, or
only the ones you name. Whichever way they answer is what gets checked, directly with the bank -
without moving money, or with a ₹1 test deposit, per your workflow. The account can be required
to be registered in one of a set of names, such as the names the GST step established. Publishes the
account as the bank states it, which can differ from what was typed.

The bank and the branch the IFSC belongs to are published when the check used the ₹1 test deposit,
which names them, and when the user uploaded a document, which prints both on its face. An account
typed in and checked without moving money carries `null` for both.

## Udyam

`udyam`

The user uploads their Udyam registration certificate, which is read for the registration number
and the enterprise it was issued to, and the number is then confirmed with the Udyam registry.
Publishes the registration as the registry states it.

## PCB certificate

`pcb-certificate`

The user uploads the consent order their state pollution control board granted them, as one PDF of the
whole order. No board publishes a register a verification can read, so the certificate is itself the
source: it is read for its registration number, the business it was issued to, the board that issued
it and the period it runs for, and the step establishes that it is still within that period and that
it was issued to the business being verified rather than to another one.

The names it may be in are the ones your workflow gives it, such as the legal and trade names a GST
registration is held under, and the certificate is scored against whichever it comes closest to. A
board issues a consent in the registered name the business applied under: a company's is in its legal
name, while a sole proprietorship's may be in the proprietor's own name or in the name the business
trades as. A name far enough from every one of them to be a different business fails the step; one
that is close without matching passes and reports its score, so a near miss is left for a person to
weigh. Which name it came closest to is published by its tag, since matching a trade name is a
different finding from matching a legal name at the same score. Expiry is judged on the day the step
is judged, so a certificate that lapses between one reading and the next has lapsed. Publishes what
the certificate states.

A workflow can give the step no names, and the certificate is then judged on its validity period
alone. Names it is given are always checked: a step that reads them from an earlier one is not asked
for until that step has published them, an empty list or null given as a fixed value or as context is
refused when the workflow is published or the verification is created, and an empty list that arrives
from an earlier step fails the step with `pcb-not-checkable`.

## Name match

`name-match`

The cross-check that every document names the same party. Each check reads a name your workflow
gives it - the holder a bank returned, the enterprise an Udyam registration is in - and scores it
against a reference name, which is the name the business is anchored on. A check reading several
names, as a GST registration does with its legal and trade names, is satisfied by whichever of them
comes closest. A name far enough from the reference to be a different business fails the step; one
that is close without matching passes and reports its score, so a near miss is left for a person to
weigh. A check whose name never arrived is not scored and not held against the step, because the step
that failed to establish it already says why. Every score is published, including on a step that
failed, so the names that matched are on the record beside the one that did not.

## DigiLocker

`digilocker`

The user shares government documents straight from DigiLocker instead of uploading them, such as
Aadhaar or PAN. You list the documents the consent asks for, and mark each one required or optional:
a missing optional document still passes the step, with the gap noted on it. The holder can be checked
against an expected PAN or a roster of named people. The PAN is checked when the holder shares it: one
the workflow requires and the holder leaves out fails the step as a missing document, and one it does
not require passes it with the gap, so a later step can ask for the PAN card instead, on a condition
that the DigiLocker step published no `pan`. A PAN document shared without a number that can be read
off it is held to the same rule under `digilocker-pan-unreadable`. A step given a PAN to check has to
ask for the PAN document. Publishes the holder's identity as the issuer states it, the Aadhaar number
masked to its last four digits, photograph included.

User-run only. The consent is granted by the holder signing in to DigiLocker themselves.

## Selfie

`selfie`

A camera capture, checked for liveness, matched against an identity document from an earlier step -
typically the PAN card - or both. Either check can be turned off; with both off, the step simply
collects the capture as evidence, such as the user holding their identity document in frame.

User-run only. What the step reads is that the face was in front of the camera, which a stored image
cannot evidence.

## Premises photos

`premises-photos`

Camera photos of the kinds you define - storefront, interior, signage - each captured with its
location, resolved to an address at the moment the photo is taken and never supplied afterwards.
Publishes the distinct places the photos were taken, for a later step to put to the user or hold
up against the addresses on record.

User-run only, because the location is fixed as the shutter falls and cannot be attached to a file
afterwards.

## Address tagging

`address-tagging`

The user labels each of a set of addresses with tags you define - head office, warehouse, not in
use. The addresses come from an earlier step, such as where the premises photos were taken. An address
already on record, such as a GST-registered one, is recognised and tagged without asking, so the
user is only asked about what could not be inferred. Publishes every address with its tag.

## Address proof

`address-proof`

Documents read as evidence that the business operates at addresses already established - a utility
bill, a lease, whatever kinds your slots ask for. Each document is read for the premises address it
states, optionally required to be in the business's name, and you state how much the documents must
prove between them: at least a number of the addresses, or every one. To collect documents without
holding them to an address, ask for them with a documents step. The addresses it stands up have to
name at least one place: an empty list or null given as a fixed value or as context is refused when the
workflow is published or the verification is created. Publishes which addresses the documents stood up
and what each document turned out to say.

## Gate check

`gate-check`

A question put to a system of your own, mid-journey, answered by what the journey should do there.
Only a hosted workflow declares one, because what it decides is whether the person walking the journey
goes on. It is always required, it cannot be put off, and it settles inline. Nor can it wait on a step
the person can put off, or on one checked in the background, whether it reads that step itself or reads
a step that does: one put off would leave the check undecided, and the person with nowhere to go, and
one still being checked would leave their screen with nothing to show until it lands.

The person meets it like any other step. Their screen answers it the moment they reach it, which puts
the question to your system, and shows the step's own title and content while your answer is on its
way. It can be reached once the values it waits for have settled, and nothing after it is part of the
journey until it has decided, so the person cannot go past it and cannot submit around it. That also
puts several checks one at a time in the order the workflow declares them.

A check is only ever asked by the person reaching it. If a value it was asked over changes, the check
goes back to waiting, and it is asked again, as a new question, when the person next reaches it. Answering
another step never asks it on the way, and neither does a background check settling.

### What your system receives

A `POST` to the checks endpoint registered for the workflow, behind the same proof its callback goes
behind, with `X-Privue-Event: verification.check` and the check's name in `X-Privue-Check`. The body
opens with the fields a [callback](/verification/callbacks#the-payload) opens with, from `event` to
`reference_user_id`, and carries the question after them:

```json theme={"system"}
{
  "event": "verification.check",
  "verification_id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
  "status": "open",
  "workflow_key": "merchant-onboarding",
  "reference_user_id": "merchant-42",
  "check": "gst-onboarded",
  "step_key": "gst-onboarded",
  "request_id": "5d0c1f0e9a0b4c55a2b6c4f1f0f8f6e2b9f8c1d2e3a4b5c6d7e8f90a1b2c3d4e",
  "asked_at": "2026-09-25T11:02:37",
  "record": { "id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301", "...": "..." }
}
```

| Field | Meaning |
| - | - |
| `check` | The question, as the workflow names it. |
| `step_key` | The step asking it. |
| `request_id` | Names one question asked by one step on one run of one verification. Every attempt at it carries the same id, and so does asking it again over the same values; asked over changed values, by another step, of another verification, or after a reset, it carries a new one. The same question can reach you more than once, as when the person's screen sends it twice, so answer a `request_id` you have already answered the same way again. |
| `asked_at` | When the question was put, in IST, the same on every attempt at it. |
| `record` | The verification exactly as `GET /verifications/{id}` would hand it back at that moment, so your service decides on all of it. |

### What you answer

Answer with an `action`, one of:

| `action` | What the journey does |
| - | - |
| `carry-on` | Carries on, and the person goes on to the next step. |
| `terminate` | Ends the run there as `gated`. The person stays on the step, shown the `title` and `content` you answered with or, where you sent none, the step's own. The `return_url` the verification was created with is not used: to send them somewhere, answer `redirect`. |
| `redirect` | Ends the run as `gated` exactly as `terminate` does, and sends the person to the `redirect_url` you answer with, where the workflow allows it. |

```json theme={"system"}
{
  "action": "redirect",
  "title": "Already onboarded",
  "content": "This GSTIN is already one of our dealers.",
  "reference_user_id": "DLR-0042",
  "redirect_url": "https://partners.example.com/dealers"
}
```

An `action` this step does not know is no answer, and the fallback below decides. A `redirect_url` on
any action but `redirect` is ignored. A heading or wording too long to show is left out and the answer
still stands, so a termination always ends the journey. `reference_user_id` is your own identifier for
whatever the check matched, published on the step in your record as you sent it, a number kept as its
text. It is never shown to the person walking the journey, since it names a record of yours, and no
later step is given its value, though a condition can still test whether you sent one; `title` and
`content` are what they see. An answer longer than 64 KB is not read as one.

A `redirect` sends the person to its `redirect_url` only where that link is on the workflow's list of
places a person may be sent, which is the same list a return URL is held to. A redirect whose link is
missing, or is not one on that list, still ends the journey, exactly as `terminate` would: the step
publishes `terminate`, sends the person nowhere, and carries the gap `gate-check-redirect-refused`,
whose `details.cause` says why:

| `cause` | What happened |
| - | - |
| `no-link` | The redirect named no `redirect_url`, or one that is blank or too long to follow. |
| `not-allowed` | The `redirect_url` is not a URL on the workflow's list, or not a URL at all. |

The record names the check that stopped it in `gated_by_step`, no later step or check is asked, and a
gated run uses no credit. Where the workflow registers a callback, a `verification.gated`
[callback](/verification/callbacks) tells you the run was stopped, whether by your answer or by the
fallback below.

### When your system does not answer

Each check is tried as the checks endpoint's registration says: by default twice, half a second apart.
A timeout, a refused connection and a `5xx` are tried again; an answer that says the request itself was
wrong, such as `404`, is not, and is a fault in how the check is asked rather than a missing answer, as
[below](#when-the-fault-is-ours). Every attempt, its timeout and the pauses between them are spent while
the person's screen waits, so a registration is held to a minute across all of them.

Where every attempt went unanswered, or your endpoint answered something that is not an answer, the
step takes the fallback your workflow declares in `when_unanswered`. Every
gate check declares one, as an `action` of its own and the wording that goes with it:

| `action` | What the journey does |
| - | - |
| `carry-on` | Carries on, exactly as `{"action": "carry-on"}` would. It takes no wording, since the person never sees a check that let them through. |
| `terminate` | Ends the run there as `gated`, exactly as `{"action": "terminate"}` would, shown the `title` and `content` it declares or, where it declares none, the step's own. |
| `redirect` | Ends the run as `gated` and sends the person to the `redirect_url` it declares, exactly as `{"action": "redirect"}` would, under the same wording. The link is held to the same list a redirect you answer is, and one not on it ends the journey as `terminate` does, carrying the gap `gate-check-redirect-refused`. |
| `hold` | Settles on nothing, and holds the person on the step until your system answers, shown the `title` and `content` it declares. See below. |

```json theme={"system"}
"when_unanswered": {
  "action": "terminate",
  "title": "We could not confirm your registration",
  "content": "Contact your account manager to continue."
}
```

A `title` is at most 120 characters and a `content` at most 500, the same as an answer's, and a
workflow declaring a longer one is refused when it is published. The wording is fixed on the step when
the check falls back and published on it just as your answer's would be, so a record reads what the
person was shown, and a later version of the workflow worded differently never changes it.

Whichever of `carry-on`, `terminate` and `redirect` decides, the step passes carrying the gap
`gate-check-unanswered`, which is how the record says your system did not answer, and its
`details.cause` says why:

| `cause` | What happened |
| - | - |
| `unreachable` | Every attempt timed out, failed to connect, or was answered with a status worth retrying. |
| `unreadable` | Your endpoint answered, but not with an answer this step can read. |

`hold` settles on no decision. The person stays on the step, which reads as an error they can
try again from, shown the `title` and `content` the hold declares where it declares them, and nothing
after it is part of their journey. Each try puts the check to your system again, under the same
`request_id`, so a hold is how the journey waits while you put right whatever kept your system from
answering. The person is never moved on or stopped by a check nobody answered, and the run stays open,
like any journey the person has yet to finish.

The step passes whichever way the decision went, because getting the journey a decision is its whole
job. It publishes `action`, the decision that stood, with the heading, wording and reference your
answer carried or the heading and wording of the fallback that stood in for it, and the `redirect_url`
on a redirect.

### When the fault is ours

The fallback is yours to decide what happens when your system cannot answer. It never decides a check
that could not be asked because of how it is registered or how we asked it, since that is no answer of
yours and no outage of yours either. These are faults on our side:

* Your endpoint refuses the request, answering `400`, `401`, `403`, `404`, `405`, `410`, `415` or `422`.
  It answered, and said the request we made cannot be served, which is a proof, a URL or a request
  that needs putting right.
* Your auth service refuses the credentials registered for it.
* The workflow registers no checks endpoint, or the check fails on our side before it reaches you.

None of them takes the fallback, so none ends the run, carries it on, or shows the person your wording.
The step reads as an error carrying the reason `gate-check-faulted`, and the person is shown the step's
own title and content with our message, which asks them to try again later. Nothing after the step is
part of their journey, and the run stays open, neither gated nor using a credit. Each try asks the
check again, so once the fault is put right the next try settles it. A person held because your system
did not answer a `hold` reads the reason `vendor-unavailable` instead, which is how a screen tells the
two apart.

A workflow whose latest version declares a gate check it cannot ask starts nothing: one that registers
no checks endpoint, or whose check falls back to a redirect whose link is not on the workflow's list of
places a person may be sent. Creating a verification on it, or resetting one onto it, is refused with
`500` and the code `workflow-misconfigured`, so the fault is found before anybody reaches the gate.

## Review

`review`

The last step of every hosted workflow. The user reads back what they provided and confirms, and
that confirmation is the submission - it cannot be given while any step is unfinished. The wording of
the screen is the workflow's own. A staged workflow has no review step: the run of the checks
submits it.

The step is also where the user declares things for themselves - that they accept your terms, that
what they provided is true. Each declaration is its own tick box, worded in Markdown so it can link
out to the terms or the policy it refers to, under a heading of its own where it needs one. One you
have not marked optional has to be ticked before the submission is accepted; an optional one records
whichever way it was left, and may be presented already ticked. A declaration you require cannot be:
accepting it is the person's own act. The answer records every declaration with the wording it was
ticked against, so a record says what was agreed to on the day rather than what the workflow says
now.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.