Uploading Media
This guide covers how to upload selfies, liveness frames, and identity documents to a ProofAge verification session. It includes file requirements, HMAC signing for multipart requests, validation error handling, and what constitutes a complete submission.
Media Types
Every upload to POST /v1/verifications/{id}/media requires a type field that identifies the kind of media being uploaded.
| Type | Purpose | Additional fields |
|---|---|---|
selfie | Primary selfie photo used for age estimation and face matching. | None |
liveness_selfie | A single frame from the liveness check sequence. | head_turn_step (optional, integer 0–10) |
document | A photo of an identity document. | side (required), document (required) |
Document Types and Sides
When type=document, you must also specify which document you are uploading and which side:
| Document type | Accepted document value | Required sides |
|---|---|---|
| National ID card | id | front and back |
| Driver's license | driver_license | front and back |
| Passport | passport | front only |
| Residence permit | residence_permit | front and back |
File Requirements
| Constraint | Value |
|---|---|
| Field name | file |
| Content type | Any image format (image/jpeg, image/png, etc.) |
| Maximum size | 10 MB |
| Encoding | multipart/form-data |
Request Format
All uploads use multipart/form-data. The form fields are:
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Always | The image file to upload. |
type | string | Always | One of: selfie, liveness_selfie, document. |
side | string | If type=document | One of: front, back. |
document | string | If type=document | One of: id, driver_license, passport, residence_permit. |
fingerprint | string | No | Optional device fingerprint, max 64 characters. |
head_turn_step | integer | No | Liveness step index (0–10). Only relevant for liveness_selfie. |
HMAC Signing for Multipart Uploads
Multipart requests use a different canonical string format than JSON requests because the raw multipart body is not deterministic. See the Authentication guide for the full specification.
The canonical string is built as:
UPPERCASE_METHOD + path + '\n' + sortedFormFields + '\n' + sortedFileHashesStep-by-step:
Method and path — Concatenate the uppercase HTTP method with the request path:
POST/v1/verifications/ver_abc123def456/mediaSorted form fields — Take all non-file fields (
type,side,document,fingerprint,head_turn_step), sort their keys alphabetically, and serialize them using RFC 3986 query string encoding:document=id&side=front&type=documentSorted file hashes — Compute the SHA-256 hash of each uploaded file's raw bytes, sort the hashes alphabetically, and join with commas:
a3f2b8c1d4e5...7890abcdef12345678Combine with newlines:
POST/v1/verifications/ver_abc123def456/media document=id&side=front&type=document a3f2b8c1d4e5...7890abcdef12345678Sign — Compute HMAC-SHA256 of the canonical string using your secret key and hex-encode the result.
WARNING
Only non-file form fields go into sortedFormFields. The file field is represented by its SHA-256 hash in sortedFileHashes.
Selfie Validation Errors
When a selfie or liveness selfie fails validation, the API returns 422 Unprocessable Entity with an error code. Handle these in your UI to guide the user toward a successful upload.
| Error code | Description | Suggested user message |
|---|---|---|
FACE_NOT_FOUND | No face was detected in the image. | "We couldn't detect a face. Please make sure your face is clearly visible and centered." |
FACE_INVALID_SIZE | The face is too small or too large relative to the frame. | "Your face is too far away or too close. Please hold your device at arm's length." |
FACE_TOO_BLURRY | The image is not sharp enough for analysis. | "The photo is blurry. Please hold your device steady and make sure the camera is focused." |
FACE_BRIGHTNESS_INVALID | The image is too dark or overexposed. | "The lighting is not right. Please move to a well-lit area and avoid direct sunlight behind you." |
FACE_OCCLUDED | Part of the face is covered (sunglasses, mask, hand, etc.). | "Something is covering your face. Please remove sunglasses, masks, or hats and try again." |
FACE_ALIGNMENT_FAILED | The face landmarks could not be extracted reliably. | "We had trouble analyzing your photo. Please face the camera directly and try again." |
FACE_TOO_TILTED | The head is rotated too far from a frontal position. | "Please look straight at the camera without tilting your head." |
FACE_VALIDATION_FAILED | A generic validation failure that does not fit the above categories. | "We couldn't process your photo. Please take a new photo in good lighting with your face clearly visible." |
Document Validation Errors
When a document photo fails validation, the API returns 422 Unprocessable Entity with an error code.
| Error code | Description | Suggested user message |
|---|---|---|
DOCUMENT_NOT_FOUND | No document was detected in the image. | "We couldn't find a document in your photo. Please place the document on a flat surface and take a clear photo." |
DOCUMENT_TOO_BLURRY | The document image is not sharp enough to read. | "The document photo is blurry. Please hold your device steady and make sure the text is readable." |
DOCUMENT_BRIGHTNESS_INVALID | The document image is too dark or washed out. | "The lighting is not right. Avoid glare and shadows, and photograph the document in even lighting." |
DOCUMENT_NOT_READABLE | The document text or photo cannot be extracted. | "We couldn't read the document. Make sure all four corners are visible and there is no glare on the surface." |
DOCUMENT_VALIDATION_FAILED | A generic document validation failure. | "We couldn't process your document. Please take a new photo with the full document visible and in focus." |
Ready for Submission
Before calling POST /v1/verifications/{id}/submit, all required media must be uploaded. The requirements depend on the verification flow:
| Flow | Required media |
|---|---|
| Age (selfie-only) | selfie |
| KYC (documents + liveness) | selfie + document front + document back |
TIP
Passports only have a front side. For passport-based KYC verifications, you need a selfie and a document front — no back is required.
If you attempt to submit before all required media is present, the API returns 422 with a message indicating which media is missing.
Re-upload Behavior
Uploading a new file of the same type and side replaces the previous upload. For example:
- Uploading a second
selfiereplaces the first selfie. - Uploading a second
documentwithside=frontanddocument=idreplaces the previous front of the ID.
This means users can fix a rejected photo without creating a new verification session. Re-uploading after a resubmission_requested status moves the verification back to started.
Examples
Upload a Selfie
curl -X POST https://api.proofage.com/v1/verifications/ver_abc123def456/media \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-HMAC-Signature: YOUR_HMAC_SIGNATURE" \
-F "type=selfie" \
-F "[email protected]"Response (201 Created):
{
"data": {
"id": "med_xyz789",
"verification_id": "ver_abc123def456",
"type": "selfie",
"mime_type": "image/jpeg",
"created_at": "2026-03-19T12:02:00Z"
}
}Upload a Document (Front of ID)
curl -X POST https://api.proofage.com/v1/verifications/ver_abc123def456/media \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-HMAC-Signature: YOUR_HMAC_SIGNATURE" \
-F "type=document" \
-F "side=front" \
-F "document=id" \
-F "file=@id_front.jpg"Response (201 Created):
{
"data": {
"id": "med_doc456",
"verification_id": "ver_abc123def456",
"type": "document",
"side": "front",
"document": "id",
"mime_type": "image/jpeg",
"created_at": "2026-03-19T12:03:00Z"
}
}Upload a Document (Back of ID)
curl -X POST https://api.proofage.com/v1/verifications/ver_abc123def456/media \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-HMAC-Signature: YOUR_HMAC_SIGNATURE" \
-F "type=document" \
-F "side=back" \
-F "document=id" \
-F "file=@id_back.jpg"Validation Error Response
If the selfie fails validation, the API returns 422:
{
"error": {
"code": "FACE_TOO_BLURRY",
"message": "The selfie image is too blurry for analysis."
}
}Blocked by Consent
If consent has not been accepted, the API returns 403:
{
"error": {
"code": "CONSENT_REQUIRED",
"message": "User consent must be recorded before uploading media."
}
}See the Consent Handling guide for how to resolve this.
Next Steps
- Consent Handling — Consent must be accepted before any media upload.
- Verification Flow — Understand the full lifecycle and what happens after submission.
- Authentication — Full details on HMAC signature computation.