Quickstart - Your First Verification Run in 5 Minutes
Get started with the Deepvue Workflows API in minutes. Find a workflow, start a run, send your customer to the journey, and read the result with cURL, Python, or Node.js.
Overview
This walkthrough takes you from credentials to a finished, readable result.
Every request carries the client-id and x-api-key headers described in Authentication.
Find the workflow you want to run
List the workflows available to your account and note the id of the one you want.
curl 'https://api.deepvue.link/v1/workflows' \
-H 'client-id: YOUR_CLIENT_ID' \
-H 'x-api-key: YOUR_API_KEY'
workflows = requests.get(f"{BASE}/v1/workflows", headers=HEADERS, timeout=30).json()
workflow_id = workflows["data"][0]["id"]
const workflows = await (await fetch(`${BASE}/v1/workflows`, { headers })).json();
const workflowId = workflows.data[0].id;
{
"data": [
{
"id": "e97e7d78-4ceb-4283-8080-5c5e0b72ddac",
"name": "Individual KYC",
"published": true,
"run_ttl_seconds": 86400,
"abandon_timeout_seconds": 3600,
"created_at": "2026-06-01T10:00:00Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
Build your result mapping before you run anything
GET /v1/workflows/{id} adds steps: every check key that can appear in a run's checks[].
steps is a superset.
Workflows branch, so a single run produces a subset of these keys.
Treat a key that never arrives as "that branch was not taken", not as an error.
Start a run
reference_id is your own identifier for the person or entity, and it is echoed back on every read.
curl -X POST 'https://api.deepvue.link/v1/workflows/WORKFLOW_ID/runs' \
-H 'Content-Type: application/json' \
-H 'client-id: YOUR_CLIENT_ID' \
-H 'x-api-key: YOUR_API_KEY' \
-d '{
"reference_id": "user_123",
"input": { "pan_number": "ABCDE1234F", "mobile": "9876543210" },
"redirect_uri": "https://your-app.example.com/kyc/done"
}'
run = requests.post(
f"{BASE}/v1/workflows/{workflow_id}/runs",
headers=HEADERS,
json={
"reference_id": "user_123",
"input": {"pan_number": "ABCDE1234F", "mobile": "9876543210"},
"redirect_uri": "https://your-app.example.com/kyc/done",
},
timeout=30,
).json()
run_id = run["id"]
journey_url = (run.get("action_required") or {}).get("url")
const response = await fetch(`${BASE}/v1/workflows/${workflowId}/runs`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({
reference_id: "user_123",
input: { pan_number: "ABCDE1234F", mobile: "9876543210" },
redirect_uri: "https://your-app.example.com/kyc/done",
}),
});
const run = await response.json();
const journeyUrl = run.action_required?.url;
The 202 response is the list shape, so it carries no checks.
{
"id": "1e30a341-cb3b-48b8-9992-99a1b436b8d9",
"workflow_id": "e97e7d78-4ceb-4283-8080-5c5e0b72ddac",
"reference_id": "user_123",
"status": "in_progress",
"started_at": "2026-07-30T09:14:02Z",
"completed_at": null,
"action_required": {
"type": "customer_input",
"signal_name": "selfie_captured",
"url": "https://hosted.deepvue.link/h/eyJ0eXAi..."
},
"error": null
}
Send the customer to the journey
For interactive workflows the response blocks briefly and already carries action_required.url, so no poll is needed to obtain the link.
If the wait times out you receive a non-terminal status with action_required: null.
Poll GET /v1/runs/{id} for the link.
redirect_uri is validated against your tenant's allowlist at request time.
Deepvue appends run_id and status to it when the run becomes terminal.
Poll until the run is terminal
Stop when status is completed, failed, expired, or cancelled.
curl 'https://api.deepvue.link/v1/runs/RUN_ID' \
-H 'client-id: YOUR_CLIENT_ID' \
-H 'x-api-key: YOUR_API_KEY'
import time
TERMINAL = {"completed", "failed", "expired", "cancelled"}
while True:
run = requests.get(f"{BASE}/v1/runs/{run_id}", headers=HEADERS, timeout=30).json()
if run["status"] in TERMINAL:
break
time.sleep(5)
const TERMINAL = new Set(["completed", "failed", "expired", "cancelled"]);
let run;
do {
run = await (await fetch(`${BASE}/v1/runs/${runId}`, { headers })).json();
if (TERMINAL.has(run.status)) break;
await new Promise((r) => setTimeout(r, 5000));
} while (true);
abandoned is not terminal, and it is the only status that can un-happen.
The run stays live and resumes the moment the customer returns.
Never infer "this run is over" from seeing it.
Prefer webhooks over polling once you are past the prototype stage.
Read the results
The detail endpoint adds checks[], reviews[], and the decision fields.
{
"id": "1e30a341-cb3b-48b8-9992-99a1b436b8d9",
"status": "completed",
"completed_at": "2026-07-30T09:16:41Z",
"action_required": null,
"checks": [
{ "key": "pan", "label": "PAN verification", "status": "passed",
"data": { "valid": true, "name_on_pan": "ANUJ RAWAT" } },
{ "key": "face_match", "label": "Face match", "status": "passed",
"data": { "match": true, "score": 0.94 } }
],
"decision": null,
"resolved_at": null,
"error": null
}
completed means the workflow reached its end, not that every check passed.
Inspect checks[].status.
Work the review queue
Some runs finish and still owe a human verdict. Poll for them, then record the decision.
curl 'https://api.deepvue.link/v1/runs?decision=review' \
-H 'client-id: YOUR_CLIENT_ID' \
-H 'x-api-key: YOUR_API_KEY'
See Reviews and decisions for the full queue semantics.