curl --request POST \
--url https://api.verify.privue.ai/verifications/{verification_id}/documents \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form 'step_key=<string>' \
--form 'slot=<string>' \
--form file=@example-file \
--form 'kind=<string>'import requests
url = "https://api.verify.privue.ai/verifications/{verification_id}/documents"
files = { "file": ("example-file", open("example-file", "rb")) }
payload = {
"step_key": "<string>",
"slot": "<string>",
"kind": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('step_key', '<string>');
form.append('slot', '<string>');
form.append('file', '<string>');
form.append('kind', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.verify.privue.ai/verifications/{verification_id}/documents', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"id": "3b9c6e1f-8a24-4d57-b0e3-7f1a5c8d2e69",
"slot": "pan-card",
"kind": "pan-card",
"label": "entity_pan",
"filename": "avesco-pan.jpg",
"content_type": "image/jpeg",
"size_bytes": 87422,
"sha256": "7a2d9c4e1f8b3a6d0e5c2b9f4a7d1e8c3b6a9d2f5e8c1b4a7d0e3f6c9b2a5d8e",
"received_at": "2026-08-27T11:05:17+05:30"
}{
"code": "body-unreadable",
"detail": "There was an error parsing the body"
}{
"code": "unauthenticated",
"detail": "Missing or malformed Authorization header"
}{
"code": "credits-exhausted",
"detail": "the credits for workflow 'merchant-onboarding' are gone; of 500 granted, 500 used"
}{
"code": "environment-mismatch",
"detail": "workflow 'merchant-onboarding' is production and this request is for uat"
}{
"code": "step-not-found",
"detail": "version 1 of workflow 'merchant-onboarding' declares no step 'pan_card'"
}{
"code": "run-in-progress",
"detail": "the checks are running on this submission; reset the verification to change it"
}{
"code": "document-too-large",
"detail": "the file exceeds the 20 MB limit"
}{
"code": "document-type-not-allowed",
"detail": "'Storefront' does not take pdf files"
}{
"code": "step-invalid",
"detail": "step 'pan' has no slot 'pan-card'"
}{
"code": "rate-limited",
"detail": "Unauthorized: RATE_LIMITED"
}{
"code": "auth-unavailable",
"detail": "Auth provider unavailable"
}Upload a document
Give us one of the documents the workflow asks for.
Name the step it belongs to and the slot on that step it answers; the workflow’s intake names both. Where that slot names several kinds of document, name which of them this is. Send a file per slot, and send several to the same slot where it takes several, as a certificate whose pages arrive as separate images does; the files in one slot are all of the same kind.
Nothing is read from a document as it arrives and no check runs on it. Upload everything you hold, then run the checks. Once you have asked for the checks to run the submission is frozen, and an upload is refused until you reset.
curl --request POST \
--url https://api.verify.privue.ai/verifications/{verification_id}/documents \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form 'step_key=<string>' \
--form 'slot=<string>' \
--form file=@example-file \
--form 'kind=<string>'import requests
url = "https://api.verify.privue.ai/verifications/{verification_id}/documents"
files = { "file": ("example-file", open("example-file", "rb")) }
payload = {
"step_key": "<string>",
"slot": "<string>",
"kind": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('step_key', '<string>');
form.append('slot', '<string>');
form.append('file', '<string>');
form.append('kind', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.verify.privue.ai/verifications/{verification_id}/documents', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"id": "3b9c6e1f-8a24-4d57-b0e3-7f1a5c8d2e69",
"slot": "pan-card",
"kind": "pan-card",
"label": "entity_pan",
"filename": "avesco-pan.jpg",
"content_type": "image/jpeg",
"size_bytes": 87422,
"sha256": "7a2d9c4e1f8b3a6d0e5c2b9f4a7d1e8c3b6a9d2f5e8c1b4a7d0e3f6c9b2a5d8e",
"received_at": "2026-08-27T11:05:17+05:30"
}{
"code": "body-unreadable",
"detail": "There was an error parsing the body"
}{
"code": "unauthenticated",
"detail": "Missing or malformed Authorization header"
}{
"code": "credits-exhausted",
"detail": "the credits for workflow 'merchant-onboarding' are gone; of 500 granted, 500 used"
}{
"code": "environment-mismatch",
"detail": "workflow 'merchant-onboarding' is production and this request is for uat"
}{
"code": "step-not-found",
"detail": "version 1 of workflow 'merchant-onboarding' declares no step 'pan_card'"
}{
"code": "run-in-progress",
"detail": "the checks are running on this submission; reset the verification to change it"
}{
"code": "document-too-large",
"detail": "the file exceeds the 20 MB limit"
}{
"code": "document-type-not-allowed",
"detail": "'Storefront' does not take pdf files"
}{
"code": "step-invalid",
"detail": "step 'pan' has no slot 'pan-card'"
}{
"code": "rate-limited",
"detail": "Unauthorized: RATE_LIMITED"
}{
"code": "auth-unavailable",
"detail": "Auth provider unavailable"
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Path Parameters
Body
One document, and where in the workflow it belongs.
The step this document answers.
1The named slot on that step it answers.
1The document itself.
Which of the slot's kinds this document is. A slot naming several needs one of them; a slot naming one takes the document as that one.
1Response
The stored file.
One file on the record, whether the user uploaded it or the step fetched it on their behalf.
The document's id, used to download it.
The named slot the file sits in.
Which of its slot's kinds the file was provided as. Every slot names at least one, and intake.slots names them for every step answered over this API. A file a check fetched carries the kind of document the source returned, and a photo taken on the person's own screens carries the kind of photo it was asked for.
That kind's name in your own system, where your workflow gives it one. The same label on the same document across branches is how you find it without knowing which branch ran.
The original filename.
The file's MIME type.
The file's size in bytes.
The SHA-256 hash of the file's contents.
When the file was received.
