Presign a Document Upload
Authorizes the upload of one identity document and returns a form to post the file to. The file goes straight to storage, never through this API.
An individual's record has three slots: face (a selfie), document-front and document-back. Request Verification needs face and document-front; document-back is for documents that have one. Presigning a slot that already holds a document replaces it, under the same document_id.
Takes a workspace-scoped API key or a dashboard session. An account-scoped key is refused with 401.
New in v3: v2 took documents through its verification link.
Authorization:
Every request is signed with your API key: a short-lived JWT, signed with your private key, sent in one header on /v3/* and /api/v2/* alike.
Headers:
Authorization: APIKey {SIGNED_JWT}
How to build and sign the JWT: Authentication.
Endpoint
/v3/customers/{customer_id}/kyc/documents/presign
Path parameters
| Parameter | Type | Description |
|---|---|---|
customer_id | string (cst_…) | Required. The customer. Its record must be open and pending or needs_update. |
Request Body Sample
{
"slot": "document-front",
"content_type": "image/png",
"size_bytes": 524288,
"file_name": "passport-front.png"
}
Request Body Schema
| Field | Type | Description |
|---|---|---|
slot | string | Required. face, document-front or document-back. |
content_type | string | Required. application/pdf, image/jpeg or image/png. The file you upload must have exactly this type. |
size_bytes | integer | Required. The file's size in bytes, greater than zero and within the per-file limit. It reserves room in the record's budget; the real size is read from storage when you complete the upload. |
file_name | string | Optional. The file's name, kept for display only. |
All of a record's documents together share one budget, because the provider receives them in a single request. A file within the per-file limit can still be refused when it does not fit next to the others. List Documents returns both limits (currently 3 MiB per file and 7 MiB in all).
Response Sample
{
"document_id": "doc_01k8x4q660fmpa2fzyf5wwq7m6",
"url": "https://uploads.example.com/kyc-documents",
"fields": {
"key": "kyc/019a3a44-93e0-709d-965e-da32dae44550/019a3a4b-98c0-7d2c-a13f-fe7979cb9e86.png",
"Content-Type": "image/png",
"policy": "eyJleHBpcmF0aW9uIjoiMjAyNS0xMC0zMVQxMjo1ODo0NFoiLCJjb25kaXRpb25zIjpbXX0=",
"X-Amz-Algorithm": "AWS4-HMAC-SHA256",
"X-Amz-Credential": "ASIAEXAMPLEKEYID/20251031/us-east-1/s3/aws4_request",
"X-Amz-Date": "20251031T124344Z",
"X-Amz-Security-Token": "IQoJb3JpZ2luX2VjEXAMPLE...",
"X-Amz-Signature": "5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924aEXAMPLE"
},
"remaining_bytes": 6815744
}
The url and fields above are illustrative: use exactly what you receive.
Response Schema
| Field | Type | Description |
|---|---|---|
document_id | string (doc_…) | The document. Use it to complete or delete it. |
url | string | Where to post the file. |
fields | object | Form fields to send with the file, verbatim. They carry the upload policy and its signature. |
remaining_bytes | integer | What is left of the record's budget after this document. |
Uploading the file
Post a multipart/form-data request to url: every entry of fields first, unchanged, then the file in a part named file, last. Anything after the file part is ignored.
curl -X POST "$URL" \
-F "key=$KEY" -F "Content-Type=image/png" -F "policy=$POLICY" \
-F "X-Amz-Algorithm=$ALGORITHM" -F "X-Amz-Credential=$CREDENTIAL" \
-F "X-Amz-Date=$DATE" -F "X-Amz-Security-Token=$TOKEN" -F "X-Amz-Signature=$SIGNATURE" \
-F "file=@passport-front.png"
Storage itself refuses an empty file, one over the per-file limit, or one of another type than content_type. The upload URL expires after a short time (15 minutes): presign again if it lapses. Then call Complete a Document Upload.
Errors
| Status | message | When |
|---|---|---|
400 | unsupported_document_content_type | content_type is not PDF, JPEG or PNG. |
400 | document_too_large | size_bytes is negative or exceeds the per-file limit. |
400 | failed to parse request | The body is not valid JSON, slot is not one of its values, or customer_id is not a cst_… TypeID. |
401 | unauthorized | Missing or invalid signature, or the caller is an account-scoped key or a customer session. |
403 | forbidden | A dashboard member whose role does not allow this. A workspace-scoped key always may. |
404 | kyc_not_found | The customer has no record: open one first. |
409 | document_budget_exceeded | The file does not fit in what is left of the record's budget. Delete or replace a document first. |
409 | kyc_verification_in_flight | The record is in_review. |
409 | kyc_already_approved | The record is approved. |
409 | kyc_rejected | The record is rejected. |
422 | Validation failed | slot, content_type or size_bytes is missing, or size_bytes is 0; details names it. |
Support and Resources
If you need assistance or have any questions, our support team is here to help. You can contact our support team at support@blockwyre.com.
Stay Updated
Stay up-to-date with the latest news, updates, and features from BlockWyre by following us on social media:
- Instagram: BlockWyre Instagram
- Twitter: BlockWyre Twitter
- Facebook: BlockWyre Facebook
- LinkedIn: BlockWyre LinkedIn
We are excited to have you on board and look forward to seeing how you leverage BlockWyre's powerful tools to enhance your financial operations. Happy integrating!