Skip to main content
GET /verifications/{verification_id} returns the whole record: the verification’s status, a tally of its steps, and each step with its state, what it collected, its documents, and why anything did not pass. Reading never changes a verification, so it is safe to poll. Poll until the status reads completed, cancelled or failed; those are the three endings. See Verification lifecycle for what each status means.
A verification expires once it has gone the expires_after_days its record reports without activity, completed ones included, and everything the user provided is deleted with it. It keeps each step’s state and reason codes while reporting no collected data and no documents. Read what you need, and download the files you need, before then. See Expiry and retention.

Step states

Which steps a verification runs, their order, and which are required come from the workflow you set up with us, so yours will differ from the examples; Step types is the menu they are drawn from. Drive your integration off the steps array the API returns, never a hardcoded list. A record reports the steps this journey has not ruled out. Your workflow declares every step any subject could need and presents each user the ones that apply to them, so a journey that took one branch reports that branch and not the others. A step is only ruled out once something has decided it. While the value that decides it has yet to arrive, the step reads undetermined, and it is reported: an open question is not a conclusion, and a record that dropped it would be claiming a branch had been settled when it had not. So a verification you have just created reports every step its workflow declares, and they resolve into the other states as the journey goes on. progress counts every step the workflow declares whichever ones the record reports, so progress.not_applicable is how many are missing from steps and progress.total is how long the journey would have been. Read the verification with include_not_applicable=true to see the ruled-out steps as well. Every step reports one of nine states: declined, not-supplied and enriching are three distinct outcomes: the user refused the step, nothing arrived for it, or it is not done yet. A step nobody was asked for is not a fourth outcome but an absence, which is why a ruled-out one is left out. A finished record holds no step that is still being checked. A hosted journey cannot be submitted while any step reads awaiting, enriching or error, so on a submitted hosted verification every step reads passed, failed or declined, and nothing was settled on the user’s behalf to make that true. A verification you supplied yourself is submitted by the run of the checks, which records a step nothing arrived for as not-supplied, required or not, and completes with a step in error only once a source has stayed unreachable for about an hour. A step whose applicability turned on that check has nothing left to settle it, so it stays undetermined on the finished record. Read not-supplied on a required step as unverified: it is the absence of a check rather than the result of one.
Steps can depend on one another. If an earlier answer changes, a dependent step is reconsidered against the new value. Drive your UI off the reported state rather than assuming a step, once passed, stays passed.
The progress object tallies every step of the journey by state, and the counts always sum to total, so you can render a progress bar without walking the array. It counts the ruled-out steps the record leaves out as well, which is what makes steps and total differ.

Reading a step

Every entry in steps has the same shape, whatever its type, so you can walk the array with one piece of code. Where a value lives is always the same: what was answered is in collected.answer and documents, what the step’s checks found is in collected.outputs, why a step did not pass is in reasons, and what a passed step passed without is in gaps. Guard for collected being null before reading either field: it is null on every step that has not been answered, including one the user declined and one the workflow never asked for. documents is always an array, empty on a step holding no files.

collected.answer

What was submitted: the fields of a form, the bank details typed, the tag chosen for an address. Its keys depend on the step’s type. It holds the latest answer only. If the step is answered again it is replaced, so answer tells you where the step stands, not what changed along the way. A step whose answer is the files it holds reports "answer": {}, because handing over the files is the whole answer. Read documents instead on those; the table below says which they are.

collected.outputs

The values you act on: the GSTIN and legal name confirmed against the GST registry, the name a bank account turned out to be registered in, the places a set of photos was taken. These are worth writing into your own systems. Three things to handle:
  • Read state first. A passed step’s values are ones its checks stood up, and they are also what the steps after it are checked against.
  • A failed step publishes what its checks found. A cross-check that failed on one name still scored the others, and those scores are here rather than only the failing one being named in reasons. Nothing after the step is checked against any of it, so treat these as the detail behind the verdict and not as values you can rely on.
  • A step can publish nothing at all. documents and selfie report {} even when they passed, and their outcome is in state. A step whose checks have not finished reports {} too, and so does one that failed before it established anything: a certificate that could not be read, or a GSTIN the registry rejected, leaves nothing to report.
A step can also be checked against what an earlier step established. That is why a step that once passed can be asked again: if the earlier answer changes, this step is checked afresh against the new value. The values here are published on different authorities. Most are what a registry, a bank or an issuer returned; a few are what a document was read to say and nothing else. See Accuracy and limits. Everything a step reports is what its checks concluded when they last ran, outputs included. A value derived from a date is worked out against the day the step was judged, so a step that has not been judged again reports the same thing however long after you read it, and a check can never contradict the state standing over it.

Custom output transformations

A workflow can publish its outputs under the key names your own schema already uses, and can convert a value into the shape your systems already read, so it lands the way you keep it. Ask us to set these up or to change them. A change to them is a new version of the workflow: the verifications created from then on arrive the new way, and each one created earlier arrives as the version it runs states. They are set per step type rather than per step, so a value reaches you the same way wherever it comes from. A workflow that asks for a PAN at several points, one per business constitution, publishes it the same way from whichever of them applied to the subject. An entry states a name, a transform, or both. A name changes the key alone. A transform converts the one value at its path with a JSONata expression: $ is that value, in the shape these guides and the API reference document it, and what the expression yields arrives in its place. This entry delivers a GST certificate’s registration_date day first, so "2019-07-01" arrives as "01/07/2019":
  • An expression reads its value and nothing else. It cannot reach another field of the step or another step. Every value no entry converts keeps the place and the shape it was published in: a list stays a list of the same length, a nested object stays nested, and nothing is regrouped, filtered or lifted to the top level. addresses renamed is still a list of {address, tag} entries.
  • Under a path that descends with [], such as filings[].filing_date, the expression runs once for each record.
  • A null arrives as null, and the expression does not run on it.
  • An expression always yields a value. Where it could find nothing, as a $lookup can for a value its table does not list, a filter can where no record matches, and a field read across a list can where the list is empty, it states what arrives instead, written ... ?? ...: $lookup({"Active": "A"}, $) ?? "OTHER" delivers "OTHER" for every status but Active.
  • A value that cannot be converted is never sent unconverted. Where a transformation cannot be applied to a verification’s value, reading that verification answers 500 with the code workflow-misconfigured. An instant run whose record cannot be converted ends as failed, which uses no credit.
These guides, every example in them, and the API reference name the fields as the system publishes them, in the shapes the system publishes them in. They are not written in your names or your shapes and never will be, so if your workflow transforms anything, every field you read about here is one you translate from rather than one you will receive. GET /workflows/{workflow_key} reports its inputs in system names too.
GET /workflows/{workflow_key} reports what the latest version has in force as output_transformations, keyed by step type. Each entry names a field’s published path and states how it reaches you, so the translation between what you read here and what you receive is available from the API itself. output_names beside it maps each path to its name alone, for the entries that state one. Transformations reach collected.outputs and nothing else. collected.answer comes back in the same shape you send it, so a field you post and read back keeps one name in both directions. documents, the code on a reasons or gaps entry, the step key, the step type and every other part of the record are untouched.

documents

The files the step holds, oldest first, whether they were uploaded or the step fetched them on the user’s behalf. Each carries its id, slot, kind, label, filename, content_type, size_bytes, sha256 and received_at. slot tells you which part of the step a file answers, such as the gst-certificate slot of a GST step or the storefront slot of a premises step. Pass the id to the download endpoint to fetch the file itself. kind tells you which of that slot’s kinds the file was provided as, so a bank account proof reads cheque or statement rather than only the slot they share. Every slot names at least one kind, so every file carries one; a file a check fetched on the user’s behalf carries the kind of document the source returned. label is that kind’s name in your own system, where your workflow gives it one, and null where it does not. Give the same label to the same document wherever a journey asks for it and you can find it without knowing which branch ran.

reasons

A failed step carries reasons, each a stable code with a readable message, and the details its message was written from:
Branch on code. The message is written for the user’s screen and its wording can change. Errors lists every code a step can report. A step whose judgement turns on several checks reports one code for each check that did not hold, so walk the array rather than reading its first entry. details is keyed per code rather than being one fixed shape, so read a key you know and leave the rest. The key worth knowing: a reason about one of a step’s document slots carries that slot’s key as slot, which is what attributes it to a single document rather than to the step as a whole. A step that collects several documents can fail on one of them, and slot is what says which:
That attribution matters where a step publishes something per document. An address proof reads each document for two things independently - whether it is the kind that was asked for, and what its address comes to - so a document can be the wrong kind and still carry a matching address. Its entry in readings says what it matched; the reason pinned to its slot says the step threw it out anyway. Read them together, or a rejected document reads as a passing one. details is empty on a record whose collected data has been cleared: the specifics go with the material, and only the code and the message are kept. reasons is empty on a step that passed and on one that was never checked, with one exception: an error step carries vendor-unavailable, could-not-verify, gate-check-faulted or check-cancelled, and each says the check did not happen rather than that anything about the subject fell short. They differ in what follows: vendor-unavailable is attempted again on its own, and could-not-verify is not, so a reset is what runs that check afresh. gate-check-faulted is a gate check held on a fault of ours, asked again each time the person tries it. check-cancelled is on a verification that ended while the check was still being made; where the verification can be reset, a reset runs it afresh too.

gaps

A passed step can carry gaps: optional pieces it asked for that never arrived, where your workflow allows that, such as a document the user chose not to share or a name that matched closely without matching exactly. Each gap is a stable code with a readable message, like a reason. Errors lists every gap code. A step with gaps still passed. Read them to know what the record is missing rather than to decide whether the step succeeded. gaps is empty on every step that did not pass.

Reading each step type

Use this to find the field a value lives in. For what each type checks, see Step types.
The field names inside a form step come from the workflow configured for your integration. Every other row of this table is the same for everyone.
Six points to build against. Take bank details from outputs, not from the answer. The published account_number and ifsc are the bank’s own, so they can differ from what was typed, and on the document path they are the only place those details appear. source tells you which intake was verified, typed or document. Route a cross-check on the score, not just the state. Every name is scored 0-100 against the reference, and two thresholds split the result three ways: at or above the upper one the name matched; below the lower one it is a different party and the step fails; between them the step passes and the score is reported, which is the near miss to put in front of a person. matches carries every score whatever the step concluded, so a step that failed on one name still shows you the ones that matched. The thresholds are configured per workflow, so read score and matched rather than assuming 85 and 60. Every name a step establishes appears twice. Once under its own field name (legal_name, registered_name, enterprise_name) so you can read a particular one, and once inside names, tagged, which is the form a cross-check scores. They are built from the same value, so they cannot disagree. Join premises captures to files by document id. Each key in locations is the id of a file in the same step’s documents, so you can put every photo next to where it was taken:
Handle the fields that only some cases produce. Where a step published its outputs, every field its type publishes is on outputs, and one with nothing to say is null, so read those for a value rather than for the key. Two cases carry no key at all, and need it tested: a step that published nothing reports {}, as the bullets above say, and a form step publishes the fields your workflow declares, so one the user left blank is absent. A digilocker step carries a pan when the user consented to their PAN, a masked_aadhaar when they consented to their Aadhaar, and a photo_document_id when the certificate they shared carries a photograph. The Aadhaar number is published as the issuer states it, masked to its last four digits. sole_proprietor_pan carries a PAN only where the business is a sole proprietorship, since that is the one constitution whose GST registration is held on an individual’s PAN. primary_business_address and other_business_address each state a place as the registry does, in parts: street, city, state, country and postal_code. The same place is in addresses as one line. primary_business_address is null where the registry returns the principal place’s address blank, and other_business_address where the registration states only a principal place. A form step’s address field publishes the address the user gave in the same parts, with country always India. business_email and business_mobile are read only where your workflow asks for the contact, and are null both where it does not ask and where the registration holds no such contact. A pan-card step publishes status, the registry’s word on whether the PAN is operative, where the registry answered at all; a step that failed because the registry could not confirm the card carries null. aadhaar_seeded says whether an Aadhaar is seeded against the PAN, and is null on a PAN held by a company, firm or trust, since only a natural person holds an Aadhaar - read that null as the question not applying rather than as an answer of false. A bank-account step publishes bank_name and branch when the account was checked with a ₹1 test deposit, which names the branch the IFSC belongs to, 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. Treat outputs as open. Read the keys you need and ignore the ones you do not recognise, so a new field never breaks your parsing.

Downloading documents

Files are gathered from whoever supplied them and returned under the step that holds them, whether the user uploaded the file or the step fetched it on their behalf. POST /verifications/{verification_id}/documents/download returns short-lived links. Send {"document_ids": [...]} for the files you want, in the order you want them back, or {} to link every file on the verification, oldest first. One expires_in_seconds covers them all. Each entry states the file the way the record does, its id, slot, kind, label, filename, content_type, size_bytes, sha256 and received_at, and adds the step_key it answers, your step_label for that step, and the url to fetch it. A download of the whole record spans every step, so each file says what it is and where it came from without your having to match ids back against the record. A call names at most 25 documents; page through a larger record in batches. If any id is not a document on that verification the call is refused with 404 and no links are issued. Fetch the files promptly. Do not cache the URLs; request fresh ones when you need the files again. For a whole finished record to read alongside this, Recipes carries one of each: a hosted record exercising eleven of the sixteen step types, and a staged one.