Submit Workflow Step
curl --request POST \
--url https://api.deepidv.com/v1/sessions/{session_id}/steps/{step_id}import requests
url = "https://api.deepidv.com/v1/sessions/{session_id}/steps/{step_id}"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.deepidv.com/v1/sessions/{session_id}/steps/{step_id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.deepidv.com/v1/sessions/{session_id}/steps/{step_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.deepidv.com/v1/sessions/{session_id}/steps/{step_id}"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.deepidv.com/v1/sessions/{session_id}/steps/{step_id}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.deepidv.com/v1/sessions/{session_id}/steps/{step_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_body{
"step_id": "ID_VERIFICATION",
"step_status": "COMPLETED",
"failure_reason": null,
"current_step": 1,
"attempts_remaining": 3,
"session_status": "PENDING",
"session_progress": "STARTED"
}
{
"step_id": "ID_VERIFICATION",
"step_status": "PENDING",
"failure_reason": "FACE_MISMATCH",
"current_step": 0,
"attempts_remaining": 2,
"session_status": "PENDING",
"session_progress": "STARTED"
}
{
"step_id": "FACE_LIVENESS",
"step_status": "IN_PROGRESS",
"failure_reason": null,
"current_step": 1,
"attempts_remaining": 3,
"session_status": "PENDING",
"session_progress": "STARTED",
"liveness": {
"liveness_session_id": "12345678-90ab-cdef-1234-567890abcdef",
"region": "us-east-1",
"credentials": {
"access_key_id": "ASIAEXAMPLE",
"secret_access_key": "secretExampleValue",
"session_token": "tokenExampleValue",
"expiration": "2026-09-08T14:20:00.000Z"
},
"expires_at": "2026-09-08T14:20:00.000Z"
}
}
{
"step_id": "FACE_LIVENESS",
"step_status": "COMPLETED",
"failure_reason": null,
"current_step": null,
"attempts_remaining": 3,
"session_status": "VERIFIED",
"session_progress": "COMPLETED",
"liveness": {
"status": "SUCCEEDED",
"confidence": 93.1,
"passed": true
}
}
{
"error": "Workflow step is out of order",
"current_step": 0,
"step_id": "FACE_LIVENESS"
}
{
"error": "Face liveness has not been started for this session",
"failure_reason": "FACE_LIVENESS_NOT_STARTED",
"current_step": 1,
"step_id": "FACE_LIVENESS"
}
Workflow Runner
Submit Workflow Step
Submit and execute one step of a workflow session
POST
/
v1
/
sessions
/
{session_id}
/
steps
/
{step_id}
Submit Workflow Step
curl --request POST \
--url https://api.deepidv.com/v1/sessions/{session_id}/steps/{step_id}import requests
url = "https://api.deepidv.com/v1/sessions/{session_id}/steps/{step_id}"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.deepidv.com/v1/sessions/{session_id}/steps/{step_id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.deepidv.com/v1/sessions/{session_id}/steps/{step_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.deepidv.com/v1/sessions/{session_id}/steps/{step_id}"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.deepidv.com/v1/sessions/{session_id}/steps/{step_id}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.deepidv.com/v1/sessions/{session_id}/steps/{step_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_body{
"step_id": "ID_VERIFICATION",
"step_status": "COMPLETED",
"failure_reason": null,
"current_step": 1,
"attempts_remaining": 3,
"session_status": "PENDING",
"session_progress": "STARTED"
}
{
"step_id": "ID_VERIFICATION",
"step_status": "PENDING",
"failure_reason": "FACE_MISMATCH",
"current_step": 0,
"attempts_remaining": 2,
"session_status": "PENDING",
"session_progress": "STARTED"
}
{
"step_id": "FACE_LIVENESS",
"step_status": "IN_PROGRESS",
"failure_reason": null,
"current_step": 1,
"attempts_remaining": 3,
"session_status": "PENDING",
"session_progress": "STARTED",
"liveness": {
"liveness_session_id": "12345678-90ab-cdef-1234-567890abcdef",
"region": "us-east-1",
"credentials": {
"access_key_id": "ASIAEXAMPLE",
"secret_access_key": "secretExampleValue",
"session_token": "tokenExampleValue",
"expiration": "2026-09-08T14:20:00.000Z"
},
"expires_at": "2026-09-08T14:20:00.000Z"
}
}
{
"step_id": "FACE_LIVENESS",
"step_status": "COMPLETED",
"failure_reason": null,
"current_step": null,
"attempts_remaining": 3,
"session_status": "VERIFIED",
"session_progress": "COMPLETED",
"liveness": {
"status": "SUCCEEDED",
"confidence": 93.1,
"passed": true
}
}
{
"error": "Workflow step is out of order",
"current_step": 0,
"step_id": "FACE_LIVENESS"
}
{
"error": "Face liveness has not been started for this session",
"failure_reason": "FACE_LIVENESS_NOT_STARTED",
"current_step": 1,
"step_id": "FACE_LIVENESS"
}
POST /v1/sessions/{session_id}/steps/{step_id}
step_id and the settings configured on that step within the workflow — see Step submission schemas below.
Steps must be submitted in order: the step_id in the URL must match the session’s current_step (as returned by Create Workflow Session or Get Workflow State), and that step must be PENDING or IN_PROGRESS.
Request
Headers
| Header | Required | Description |
|---|---|---|
x-api-key | Yes | Your organization’s API key |
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string (uuid) | Yes | The session to submit a step for |
step_id | string | Yes | ID_VERIFICATION or FACE_LIVENESS — must match the session’s current step |
Body parameters
The request body depends onstep_id:
step_id | Body schema |
|---|---|
ID_VERIFICATION | ID_VERIFICATION submission |
FACE_LIVENESS | FACE_LIVENESS submission |
Step submission schemas
ID_VERIFICATION
Get
uploads values from Create Session Uploads — upload each image to its presigned URL first, then submit the returned file_key values here.| Field | Type | Required | Description |
|---|---|---|---|
document_type | string | Yes | The type of the primary document being submitted, e.g. passport-us |
secondary_document_type | string | Only if the step requires a secondary ID | Type of the secondary document |
tertiary_document_type | string | Only if the step requires a tertiary ID | Type of the tertiary document |
uploads.id_front | string | Yes | File key for the primary document’s front image |
uploads.id_back | string | Yes, unless document_type is a front-only type (see front_only_document_types in requirements) | File key for the primary document’s back image. Rejected if supplied for a front-only document type |
uploads.secondary_id_front | string | Only if a secondary ID is required | File key for the secondary document’s front image |
uploads.secondary_id_back | string | Same front/back rule as id_back, keyed off secondary_document_type | File key for the secondary document’s back image |
uploads.tertiary_id_front | string | Only if a tertiary ID is required | File key for the tertiary document’s front image |
uploads.tertiary_id_back | string | Same front/back rule as id_back, keyed off tertiary_document_type | File key for the tertiary document’s back image |
uploads.selfie_front | string | Yes | File key for the selfie image |
requirements from Create Workflow Session or Get Workflow State.
curl -X POST https://api.deepidv.com/v1/sessions/80e801af-991b-425c-96fc-f5e07d794253/steps/ID_VERIFICATION \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"document_type": "passport-us",
"uploads": {
"id_front": "3f9c1e2a-org/80e801af.../1717000000000-id_front.jpg",
"selfie_front": "3f9c1e2a-org/80e801af.../1717000000001-selfie_front.jpg"
}
}'
const response = await fetch(
"https://api.deepidv.com/v1/sessions/80e801af-991b-425c-96fc-f5e07d794253/steps/ID_VERIFICATION",
{
method: "POST",
headers: {
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
document_type: "passport-us",
uploads: {
id_front: "3f9c1e2a-org/80e801af.../1717000000000-id_front.jpg",
selfie_front: "3f9c1e2a-org/80e801af.../1717000000001-selfie_front.jpg",
},
}),
}
);
const result = await response.json();
import requests
response = requests.post(
"https://api.deepidv.com/v1/sessions/80e801af-991b-425c-96fc-f5e07d794253/steps/ID_VERIFICATION",
headers={"x-api-key": "YOUR_API_KEY"},
json={
"document_type": "passport-us",
"uploads": {
"id_front": "3f9c1e2a-org/80e801af.../1717000000000-id_front.jpg",
"selfie_front": "3f9c1e2a-org/80e801af.../1717000000001-selfie_front.jpg",
},
},
)
result = response.json()
FACE_LIVENESS
The body is a discriminated union onaction. All variants also accept frame_count and clip_mime_type, but those are only meaningful — and only validated — when action is upload-url.
action | Fields | Description |
|---|---|---|
start | none | Mints a Rekognition Face Liveness session and returns streaming credentials for the client SDK |
complete | none | Fetches the liveness result server-side once the client has finished streaming |
upload-url | frame_count (integer, 1-8, required), clip_mime_type (string, optional) | Custom liveness provider only — mints upload URLs for captured frames/clip. Not used with the default provider |
curl -X POST https://api.deepidv.com/v1/sessions/80e801af-991b-425c-96fc-f5e07d794253/steps/FACE_LIVENESS \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "action": "start" }'
curl -X POST https://api.deepidv.com/v1/sessions/80e801af-991b-425c-96fc-f5e07d794253/steps/FACE_LIVENESS \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "action": "complete" }'
const start = await fetch(
"https://api.deepidv.com/v1/sessions/80e801af-991b-425c-96fc-f5e07d794253/steps/FACE_LIVENESS",
{
method: "POST",
headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" },
body: JSON.stringify({ action: "start" }),
}
);
// ... stream the liveness session with the returned credentials, then:
const complete = await fetch(
"https://api.deepidv.com/v1/sessions/80e801af-991b-425c-96fc-f5e07d794253/steps/FACE_LIVENESS",
{
method: "POST",
headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" },
body: JSON.stringify({ action: "complete" }),
}
);
import requests
headers = {"x-api-key": "YOUR_API_KEY"}
base = "https://api.deepidv.com/v1/sessions/80e801af-991b-425c-96fc-f5e07d794253/steps/FACE_LIVENESS"
start = requests.post(base, headers=headers, json={"action": "start"}).json()
# ... stream the liveness session with the returned credentials, then:
complete = requests.post(base, headers=headers, json={"action": "complete"}).json()
Response
The response is a common envelope for both step types; FACE_LIVENESS additionally includes aliveness object.
200 — Success
| Field | Type | Present when | Description |
|---|---|---|---|
step_id | string | always | ID_VERIFICATION or FACE_LIVENESS |
step_status | string | always | PENDING, IN_PROGRESS, COMPLETED, FAILED, or SKIPPED |
failure_reason | string | null | always | Reason the step failed this attempt, or null. See ID_VERIFICATION failure reasons and FACE_LIVENESS failure reasons |
current_step | integer | null | always | 0-based index of the next step to submit. null once the run has finished |
attempts_remaining | number | null | always | Session-wide failure budget remaining. null when the workflow has no failure limit configured |
session_status | string | always | Session status: PENDING, SUBMITTED, VERIFIED, REJECTED, VOIDED, EXPIRED, or FAILED |
session_progress | string | always | PENDING, STARTED, or COMPLETED |
liveness | object | step_id is FACE_LIVENESS | Shape depends on the submitted action — see FACE_LIVENESS results |
Advisory checks (age, expiry, document validity period, state) never fail an
ID_VERIFICATION step and are not included in the response — only the blocking checks below can produce a failure_reason.ID_VERIFICATION failure reasons
failure_reason | Meaning |
|---|---|
FACE_NOT_DETECTED_IN_ID | No face was detected on the primary document’s front image |
OCR_NAME_MISSING | No first/last/middle name could be extracted from the document |
ID_CLASSIFICATION_FAILED | The document’s detected type doesn’t match, or isn’t an accepted, document type |
ID_TYPE_UNRECOGNIZED | The document’s type field couldn’t be read |
ID_TYPE_LOW_CONFIDENCE | The document’s type field was read with too little confidence |
DOC_TYPE_NOT_ALLOWED | The detected document type doesn’t match the declared document_type family |
NO_FACE_DETECTED | No face was detected in the selfie image |
FACE_MISMATCH | The selfie face doesn’t match the document’s face closely enough |
LIVENESS_ID_MISMATCH | The face captured during the FACE_LIVENESS step doesn’t match the document’s face |
FACE_LIVENESS results
liveness depends on the submitted action:
start
| Field | Type | Description |
|---|---|---|
liveness_session_id | string | The Rekognition Face Liveness session ID the client streams to |
region | string | AWS region for the liveness session, e.g. us-east-1 |
credentials.access_key_id | string | Temporary AWS access key |
credentials.secret_access_key | string | Temporary AWS secret key |
credentials.session_token | string | Temporary AWS session token |
credentials.expiration | string | ISO 8601 expiry of the credentials (~15 minutes) |
expires_at | string | ISO 8601 expiry of the liveness session |
complete
| Field | Type | Description |
|---|---|---|
status | string | Normalized Rekognition liveness status, e.g. SUCCEEDED or FAILED |
confidence | number | null | Liveness confidence score (0-100), or null when Rekognition didn’t return one |
passed | boolean | true when status is SUCCEEDED and confidence meets the step’s configured threshold |
FACE_LIVENESS failure reasons
failure_reason | Meaning |
|---|---|
FACE_LIVENESS_CHECK_FAILED | The liveness check completed but did not pass (passed: false). Returned in a 200 response, not an error |
Calling
complete before start, or before Rekognition has finished processing, returns a 409 with failure_reason of FACE_LIVENESS_NOT_STARTED or FACE_LIVENESS_RESULT_NOT_READY — see Error responses. These are retryable and don’t consume an attempt.Error responses
| Status | Description |
|---|---|
400 | Invalid session_id/step_id path parameters, the request body fails the step’s schema, or an upload key in the body doesn’t belong to this session |
401 | Invalid or missing API key |
403 | The session belongs to a different organization |
404 | Session not found, step_id isn’t ID_VERIFICATION/FACE_LIVENESS, the step isn’t part of this session’s workflow, or no workflow execution state exists for this session |
409 | The session is terminal, the step is out of order or not in a submittable state, a concurrent submission conflict occurred, or (FACE_LIVENESS only) complete was called before start or before the result was ready |
429 | Too many requests |
501 | This step type isn’t runnable through the API yet |
{
"step_id": "ID_VERIFICATION",
"step_status": "COMPLETED",
"failure_reason": null,
"current_step": 1,
"attempts_remaining": 3,
"session_status": "PENDING",
"session_progress": "STARTED"
}
{
"step_id": "ID_VERIFICATION",
"step_status": "PENDING",
"failure_reason": "FACE_MISMATCH",
"current_step": 0,
"attempts_remaining": 2,
"session_status": "PENDING",
"session_progress": "STARTED"
}
{
"step_id": "FACE_LIVENESS",
"step_status": "IN_PROGRESS",
"failure_reason": null,
"current_step": 1,
"attempts_remaining": 3,
"session_status": "PENDING",
"session_progress": "STARTED",
"liveness": {
"liveness_session_id": "12345678-90ab-cdef-1234-567890abcdef",
"region": "us-east-1",
"credentials": {
"access_key_id": "ASIAEXAMPLE",
"secret_access_key": "secretExampleValue",
"session_token": "tokenExampleValue",
"expiration": "2026-09-08T14:20:00.000Z"
},
"expires_at": "2026-09-08T14:20:00.000Z"
}
}
{
"step_id": "FACE_LIVENESS",
"step_status": "COMPLETED",
"failure_reason": null,
"current_step": null,
"attempts_remaining": 3,
"session_status": "VERIFIED",
"session_progress": "COMPLETED",
"liveness": {
"status": "SUCCEEDED",
"confidence": 93.1,
"passed": true
}
}
{
"error": "Workflow step is out of order",
"current_step": 0,
"step_id": "FACE_LIVENESS"
}
{
"error": "Face liveness has not been started for this session",
"failure_reason": "FACE_LIVENESS_NOT_STARTED",
"current_step": 1,
"step_id": "FACE_LIVENESS"
}