> ## 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.

# Try an instant workflow

> Run a whole verification in one call from a notebook, using documents you already have.

For trying the API. Every block below is a notebook cell: open a Jupyter notebook, save a few real
documents beside it, and run the cells in order. Nothing here is an integration pattern, just the
shortest path to seeing a real record.

You need a workflow whose `mode` is `instant`. `GET /workflows` lists yours with the mode of each,
along with how many credits each has left. The documents you send are read as they arrive and stored
nowhere, so nothing you run here leaves a file behind.

Reach for your `uat` key, whose token begins `uat_`, so the runs below come out of a `uat` workflow's
credits rather than out of what you bought for real work. Whichever key you use, the keys below are
placeholders for your own, and a `uat` workflow's key ends `-uat`.

The verification is real either way. There is no sandbox: your documents really are read, and really
are confirmed with the bodies that issued them.

```python theme={"system"}
!pip install httpx
```

```python theme={"system"}
import base64
import mimetypes
import os

import httpx

os.environ["PRIVUE_API_KEY"] = "your key"

privue = httpx.Client(
    base_url="https://api.verify.privue.ai",
    headers={"Authorization": f"Bearer {os.environ['PRIVUE_API_KEY']}"},
    timeout=200,
)
```

<Warning>
  The timeout is 200 seconds on purpose. The checks are document reads and calls
  to the issuing registries, and they run before this call answers, so a client
  on the usual 30-second default gives up on a verification that is still
  running - and it runs to completion, and is charged for, whether or not you
  are still listening. Step 6 is how you find one you hung up on.
</Warning>

Every error comes back as `{"detail": "..."}`. A cell that fails raises on the response it already
kept in `response`, so `response.json()["detail"]` in a new cell says what went wrong.

## 1. See what the workflow asks for

Read the workflow first. Its `mode` says whether you run it this way, and each step's `intake` says how
it is answered: the slots its documents go in, with how many files each takes and in what formats, and
the values it takes.

```python theme={"system"}
response = privue.get("/workflows/vendor-check")
response.raise_for_status()
workflow = response.json()

print(workflow["mode"])
for step in workflow["steps"]:
    slots = ", ".join(slot["key"] for slot in step["intake"]["slots"]) or "-"
    values = ", ".join(step["intake"]["values"]) or "-"
    print(f"{step['key']:14} required={str(step['required']):5} slots={slots:18} values={values}")
```

```text theme={"system"}
instant
gst            required=False slots=gst-certificate    values=-
pan            required=True  slots=pan-card           values=-
bank           required=False slots=bank-account-proof values=account_number, ifsc
udyam          required=False slots=udyam-certificate  values=-
cross-check    required=True  slots=-                  values=-
```

A step with one slot takes its document without being told which; you only name a `slot` where a step
has more than one. A step with no slots and no values, like `cross-check`, is answered from what the
steps before it established, and you send nothing for it.

## 2. Read your documents into the request

Each document carries its own bytes, base64-encoded, and names the step it answers.

```python theme={"system"}
def carry(step_key, path):
    """One document as the request carries it: the step it answers, and the file itself."""
    content_type, _ = mimetypes.guess_type(path)
    with open(path, "rb") as handle:
        return {
            "step_key": step_key,
            "filename": path,
            "content_type": content_type,
            "content": base64.b64encode(handle.read()).decode(),
        }


documents = [
    carry("gst", "gst-certificate.pdf"),
    carry("pan", "pan-card.jpg"),
]

for document in documents:
    print(f"{document['step_key']:6} {document['filename']:24} {document['content_type']}")
```

Send several documents for the same slot where it takes several, as a certificate whose pages arrive as
separate images does. Up to 12 documents in one request, and 25 MB between them once decoded.

The bank step is left out of the documents on purpose: it takes typed details or a document, and the
request below sends the details. To answer it with a cancelled cheque instead, add it to `documents`
with `"kind": "cheque"`, since its slot names more than one kind of proof, and drop the `bank` entry
from `steps`.

## 3. Send it

One call carries the workflow, your reference for the subject, the values the steps take and every
document. It answers with the finished record.

```python theme={"system"}
response = privue.post(
    "/verifications/instant",
    json={
        "workflow_key": "vendor-check",
        "reference_user_id": "VENDOR-4471",
        "steps": {"bank": {"account_number": "50100234567890", "ifsc": "HDFC0001234"}},
        "documents": documents,
    },
)
response.raise_for_status()
record = response.json()

print(record["status"], record["progress"])
```

```text theme={"system"}
completed {'total': 5, 'passed': 4, 'failed': 0, 'awaiting': 0, 'enriching': 0, 'error': 0, 'declined': 0, 'not_supplied': 1, 'undetermined': 0, 'not_applicable': 0}
```

The cell holds while the checks run, and `record` is the finished thing. Nothing to poll, and no
callback for this call.

The call used one credit, because the run it made both started and ended inside it:

```python theme={"system"}
print(record["credits_consumed"])  # 1
```

A `402` instead of a record means the workflow has no credits left, and no run was made. Read the
workflow for `credits.remaining` and `credits.expiring`; retrying does not clear it.

A `409` means the workflow is already running as many verifications as it may at once. Each call holds
a place for as long as it takes to answer, so `runs.limit` is how many of these you may have in flight;
keep at most that many calls open at a time. See [Credits and limits](/verification/credits).

If a call never returns an answer at all, the run it started was still made. Find its record by the
reference you sent, in step 6 below, rather than sending the submission again: a second call is a
second verification and a second credit.

## 4. Read what it found

Every step reports a state and, where it did not pass cleanly, the reasons why.

```python theme={"system"}
for step in record["steps"]:
    notes = [note["message"] for note in step["reasons"] + step["gaps"]]
    print(f"{step['key']:14} {step['state']:14} {' | '.join(notes)}")
```

```text A run worth a second look theme={"system"}
gst            passed
pan            passed
bank           passed
udyam          not-supplied
cross-check    passed         Bank account holder reads AVESCO HOSPITEX, which is close but not an exact match (65% match).
```

A step that `passed` with a **gap** is the case worth routing to a person: every document is real and
confirmed at source, but something is not an exact match. A step that `failed` carries the **reasons**
it did not stand up. A step in `error` was never checked, because a source could not be reached or
there was no time left to reach it.

Cross-check scores are on the step's outputs, if you want the numbers rather than the sentences:

```python theme={"system"}
for step in record["steps"]:
    if step["type"] == "name-match" and step["collected"]:
        for match in step["collected"]["outputs"]["matches"]:
            print(f"{match['check']:22} {match['score']:>3}%  matched={match['matched']}")
```

## 5. Ask again with something different

Every call runs. Send the same subject with a different set and you get a second verification of them,
answered on what that call carried.

```python theme={"system"}
response = privue.post(
    "/verifications/instant",
    json={
        "workflow_key": "vendor-check",
        "reference_user_id": "VENDOR-4471",
        "documents": [carry("pan", "pan-card.jpg")],
    },
)
response.raise_for_status()
again = response.json()

print(again["id"] != record["id"], again["reference_user_id"])
```

```text theme={"system"}
True VENDOR-4471
```

A new record, under the same reference. Nothing was reused from the first call and nothing was reset:
`reference_user_id` is your label for the subject here rather than a key, so several records carry it.

## 6. Find a run you did not see the answer to

A call that reached us ran the checks whether or not the reply got back to you. List by the reference to
find it.

```python theme={"system"}
response = privue.get("/verifications", params={"reference_user_id": "VENDOR-4471"})
response.raise_for_status()

for entry in response.json()["verifications"]:
    print(f"{entry['id']}  {entry['created_at']}  {entry['status']}")
```

Newest first, so the run you just made is at the top. Read any of them in full with
`GET /verifications/{verification_id}`.

## 7. See what a bad submission does

Leave out a document the workflow requires and the request is refused before anything runs.

```python theme={"system"}
response = privue.post(
    "/verifications/instant",
    json={
        "workflow_key": "vendor-check",
        "reference_user_id": "VENDOR-4471",
        "documents": [carry("gst", "gst-certificate.pdf")],
    },
)
print(response.status_code, response.json().get("detail", "accepted"))
```

```text theme={"system"}
422 pan: nothing was supplied for this step
```

Nothing was checked and nothing was charged for, and no record was created. The same `422` covers a
slot you filled only part of, which reads `gst: slot 'gst-certificate' needs 2 more file(s)`, a step
given both values and a document, and a document naming a step your workflow does not declare. Correct
the request and send it again.

<Warning>
  A file the step cannot use is refused the same way, naming it by its position:
  `415` for a format the slot does not take, `413` for one over the size limit,
  which is 20 MB for a PDF and 10 MB for an image.
</Warning>

## 8. Purge it

The documents were never kept, but the values you sent and what was read off each file are on the
record. Purge deletes them and closes it for audit.

```python theme={"system"}
response = privue.post(f"/verifications/{record['id']}/purge")
response.raise_for_status()
purged = response.json()
print(purged["status"], purged["expired_by"])  # expired client
```

## The record you just read

The finished record behind the run above, from the `vendor-check` workflow. Yours differs: which steps
a verification runs comes from the workflow configured for your integration. The envelope around them
is the same for everyone.

<Accordion title="POST /verifications/instant">
  ```json theme={"system"}
  {
    "id": "0b4d7c62-9a15-4e38-b7f0-3c8e1d05a94b",
    "mode": "instant",
    "status": "completed",
    "workflow_key": "vendor-check",
    "version": 1,
    "mobile": null,
    "reference_user_id": "VENDOR-4471",
    "context": {},
    "return_url": null,
    "journey_url": null,
    "created_at": "2026-08-27T11:02:09",
    "run_requested_at": "2026-08-27T11:02:09",
    "submitted_at": "2026-08-27T11:03:27",
    "completed_at": "2026-08-27T11:03:27",
    "cancelled_at": null,
    "gated_at": null,
    "gated_by_step": null,
    "failed_at": null,
    "expired_at": null,
    "expired_by": null,
    "last_activity_at": "2026-08-27T11:02:09",
    "expires_after_days": 30,
    "acknowledgement": null,
    "callback": null,
    "credits_consumed": 1,
    "resets": 0,
    "progress": {
      "total": 5,
      "passed": 4,
      "failed": 0,
      "awaiting": 0,
      "enriching": 0,
      "error": 0,
      "declined": 0,
      "not_supplied": 1,
      "undetermined": 0,
      "not_applicable": 0
    },
    "steps": [
      {
        "key": "gst",
        "title": "GST certificate",
        "label": null,
        "type": "gst-certificate",
        "required": false,
        "state": "passed",
        "reasons": [],
        "gaps": [],
        "collected": {
          "answer": {},
          "outputs": {
            "gstin": "27AAECA9012P1ZK",
            "legal_name": "AVESCO HOSPITEX PRIVATE LIMITED",
            "trade_name": "Avesco Hospitex",
            "registration_status": "Active",
            "registration_date": "2019-07-02",
            "business_constitution": "Private Limited Company",
            "taxpayer_type": "Regular",
            "e_invoice_mandated": true,
            "aggregate_turnover": "Slab: Rs. 5 Cr. to 25 Cr.",
            "authorized_signatories": [
              "Anil Deshpande",
              "Sunita Deshpande"
            ],
            "nature_of_business_activities": [
              "Wholesale Business",
              "Supplier of Services"
            ],
            "business_details": {
              "goods_details": [
                {
                  "goods_description": "Hospital furniture",
                  "hsn_code": "94029090"
                }
              ],
              "service_details": [
                {
                  "service_description": "Installation services of other goods",
                  "sac_code": "995479"
                }
              ]
            },
            "filings": [
              {
                "return_type": "GSTR1",
                "financial_year": "2026-2027",
                "tax_period": "July",
                "status": "Filed",
                "filing_date": "2026-08-09",
                "mode_of_filing": "ONLINE"
              },
              {
                "return_type": "GSTR3B",
                "financial_year": "2026-2027",
                "tax_period": "July",
                "status": "Filed",
                "filing_date": "2026-08-19",
                "mode_of_filing": "ONLINE"
              },
              {
                "return_type": "GSTR9",
                "financial_year": "2024-2025",
                "tax_period": "Annual",
                "status": "Filed",
                "filing_date": "2025-12-18",
                "mode_of_filing": "ONLINE"
              }
            ],
            "business_pan": "AAECA9012P",
            "sole_proprietor_pan": null,
            "addresses": [
              {
                "address": "Plot 14, MIDC Bhosari, Pune, Maharashtra 411026",
                "tag": "Principal Business Address"
              }
            ],
            "primary_business_address": {
              "street": "Plot 14, MIDC Bhosari",
              "city": "Pune",
              "state": "Maharashtra",
              "country": "India",
              "postal_code": "411026"
            },
            "other_business_address": null,
            "names": [
              {
                "name": "AVESCO HOSPITEX PRIVATE LIMITED",
                "tag": "Legal Name"
              },
              {
                "name": "Avesco Hospitex",
                "tag": "Trade Name"
              }
            ],
            "promoters": [
              "Anil Deshpande",
              "Sunita Deshpande"
            ],
            "business_email": "accounts@avescohospitex.in",
            "business_mobile": "9822045678"
          }
        },
        "documents": [
          {
            "id": "d1e4a7b2-5c38-4f9e-8a61-2b7c0d3e9f14",
            "slot": "gst-certificate",
            "kind": "gst-certificate",
            "label": null,
            "filename": "gst-certificate.pdf",
            "content_type": "application/pdf",
            "size_bytes": 203118,
            "sha256": "1c8f4e2a9b7d63f05e1a4c8b2d7f9e3a6b5c0d1e2f3a4b5c6d7e8f9a0b1c2d3e",
            "received_at": "2026-08-27T11:02:09"
          }
        ]
      },
      {
        "key": "pan",
        "title": "PAN card",
        "label": null,
        "type": "pan-card",
        "required": true,
        "state": "passed",
        "reasons": [],
        "gaps": [],
        "collected": {
          "answer": {},
          "outputs": {
            "pan": "AAECA9012P",
            "name": "AVESCO HOSPITEX PRIVATE LIMITED",
            "date_of_birth_or_incorporation": "2019-05-21",
            "card_document_id": "3b9c6e1f-8a24-4d57-b0e3-7f1a5c8d2e69",
            "status": "valid",
            "aadhaar_seeded": null,
            "names": [
              {
                "name": "AVESCO HOSPITEX PRIVATE LIMITED",
                "tag": "PAN Holder"
              }
            ]
          }
        },
        "documents": [
          {
            "id": "3b9c6e1f-8a24-4d57-b0e3-7f1a5c8d2e69",
            "slot": "pan-card",
            "kind": "pan-card",
            "label": null,
            "filename": "pan-card.jpg",
            "content_type": "image/jpeg",
            "size_bytes": 87422,
            "sha256": "7a2d9c4e1f8b3a6d0e5c2b9f4a7d1e8c3b6a9d2f5e8c1b4a7d0e3f6c9b2a5d8e",
            "received_at": "2026-08-27T11:02:09"
          }
        ]
      },
      {
        "key": "bank",
        "title": "Bank account",
        "label": null,
        "type": "bank-account",
        "required": false,
        "state": "passed",
        "reasons": [],
        "gaps": [],
        "collected": {
          "answer": {
            "source": "typed",
            "account_number": "50100234567890",
            "ifsc": "HDFC0001234"
          },
          "outputs": {
            "source": "typed",
            "account_number": "50100234567890",
            "ifsc": "HDFC0001234",
            "registered_name": "AVESCO HOSPITEX",
            "bank_name": null,
            "branch": null,
            "names": [
              {
                "name": "AVESCO HOSPITEX",
                "tag": "Bank Account Holder"
              }
            ]
          }
        },
        "documents": []
      },
      {
        "key": "udyam",
        "title": "Udyam certificate",
        "label": null,
        "type": "udyam",
        "required": false,
        "state": "not-supplied",
        "reasons": [],
        "gaps": [],
        "collected": null,
        "documents": []
      },
      {
        "key": "cross-check",
        "title": "Name cross-check",
        "label": null,
        "type": "name-match",
        "required": true,
        "state": "passed",
        "reasons": [],
        "gaps": [
          {
            "code": "name-match-inexact",
            "message": "Bank account holder reads AVESCO HOSPITEX, which is close but not an exact match (65% match).",
            "details": {
              "check": "Bank account holder",
              "name": "AVESCO HOSPITEX",
              "score": 65
            }
          }
        ],
        "collected": {
          "answer": {},
          "outputs": {
            "reference": [
              {
                "name": "AVESCO HOSPITEX PRIVATE LIMITED",
                "tag": "Legal Name"
              }
            ],
            "matches": [
              {
                "check": "Bank account holder",
                "name": "AVESCO HOSPITEX",
                "matched_to_tag": "Legal Name",
                "score": 65,
                "matched": false
              }
            ]
          }
        },
        "documents": []
      }
    ]
  }
  ```
</Accordion>

* **Nobody signs in, so `mobile`, `return_url` and `journey_url` are null**, and `callback` is null
  too: the reply carried the result, so there was nothing to hand off.
* **`run_requested_at` is when the request arrived**, and `completed_at` is a minute or so later. The
  whole run happened between them, inside the call.
* **`udyam` is `not-supplied`.** Nothing arrived for it and it is optional, so the run closed over it.
  `collected` is `null`, no source was called, and it is not a verdict about the business - only the
  absence of a check.
* **The cross-check passed with a gap.** The bank returned `AVESCO HOSPITEX` where the business is
  registered as `AVESCO HOSPITEX PRIVATE LIMITED`: close enough not to be a different business, not
  close enough to call an exact match. `outputs.reference` names which name it scored against and
  `outputs.matches` carries the score behind every check, so you can route on the number rather than
  the sentence.
* **The documents carry no bytes you can reach.** Each one names its step, slot, type, size and hash,
  which is what the record keeps once the files are gone.

[Reading the result](/verification/results) is the field-by-field reference for a step.

<Info>
  When you move from trying it to building against it, read
  [Instant](/verification/instant) for what is decided for you and why, and
  [Going live](/verification/going-live) before your first real subject.
</Info>


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