Documents
Before Corvo can print and ship a document, you must upload the PDF or create a tracked presigned upload target. Every shipment references a previously validated `document_key`.
Document metadata object
Returned by upload and confirm
| Name | Type | Required | Description |
|---|---|---|---|
| data.document_key | string | Yes | Opaque key you pass to shipment creation. It is not a public download URL. |
| data.filename | string | Yes | Normalized PDF filename stored with the upload. |
| data.page_count | integer | Yes | Validated page count extracted from the uploaded PDF. |
| data.size_bytes | integer | Yes | Uploaded file size in bytes. |
Direct multipart upload
Use direct upload when your backend already has the PDF bytes and does not need a browser-to-S3 handoff.
/api/v1/documents/uploadUpload a PDF directly as multipart form data.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| file | file | Yes | PDF document uploaded as multipart/form-data. |
multipart/form-data.curl -X POST https://corvo.to/api/v1/documents/upload \
-H "Authorization: Bearer $CORVO_API_KEY" \
-F "file=@/path/to/notice.pdf"{
"data": {
"document_key": "documents/7b2f4a1e-9c3d-4e5f-8a6b-1c2d3e4f5a6b/a8e4f2c1-3b5d-4e7f-9a1b-2c3d4e5f6a7b.pdf",
"filename": "notice.pdf",
"page_count": 12,
"size_bytes": 245760
}
}{
"error": {
"message": "Encrypted PDFs are not supported",
"code": "ENCRYPTED_PDF"
}
}Presigned upload flow
Use the tracked presigned flow when a browser or a separate worker should upload directly to S3. This is a three-step process: create-target, upload to S3, then confirm.
Step 1: Create an upload target
/api/v1/documents/upload-urlCreate a short-lived S3 form POST target for a PDF upload.
Request body (optional JSON)
| Name | Type | Required | Description |
|---|---|---|---|
| filename | string | No | Original PDF filename. Corvo normalizes the name and preserves a `.pdf` extension. |
Response schema
| Name | Type | Required | Description |
|---|---|---|---|
| data.upload_id | string (UUID) | Yes | Tracked upload ID you later confirm with `/documents/confirm`. |
| data.upload_url | string | Yes | S3 form POST target. Step 2 is sent here, not to Corvo. |
| data.fields | object | Yes | Form fields that must be echoed back exactly to S3. |
| data.expires_in | integer | Yes | Seconds until the S3 form POST target expires. |
curl -X POST https://corvo.to/api/v1/documents/upload-url \
-H "Authorization: Bearer $CORVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename":"notice.pdf"}'{
"data": {
"upload_id": "550e8400-e29b-41d4-a716-446655440000",
"upload_url": "https://corvo-documents.s3.amazonaws.com/",
"expires_in": 3600,
"fields": {
"key": "documents/pending/org-id/550e8400-e29b-41d4-a716-446655440000.pdf",
"Content-Type": "application/pdf",
"Content-Disposition": "inline; filename=\"notice.pdf\"",
"Policy": "...",
"X-Amz-Algorithm": "AWS4-HMAC-SHA256",
"X-Amz-Credential": "...",
"X-Amz-Date": "20260309T150000Z",
"X-Amz-Signature": "..."
}
}
}Step 2: POST the file to S3
This request goes to the returned S3 upload_url, not to Corvo. Include every returned field exactly as given, then append the file part last.
curl -X POST "$UPLOAD_URL" \
-F "key=$KEY" \
-F "Content-Type=application/pdf" \
-F "Content-Disposition=$CONTENT_DISPOSITION" \
-F "Policy=$POLICY" \
-F "X-Amz-Algorithm=$X_AMZ_ALGORITHM" \
-F "X-Amz-Credential=$X_AMZ_CREDENTIAL" \
-F "X-Amz-Date=$X_AMZ_DATE" \
-F "X-Amz-Signature=$X_AMZ_SIGNATURE" \
-F "file=@/path/to/notice.pdf;type=application/pdf"POST /api/v1/documents/confirm so Corvo can validate the PDF and issue a final document_key.Step 3: Confirm the upload
/api/v1/documents/confirmValidate a tracked S3 upload and return a final document key.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| upload_id | string (UUID) | Yes | Tracked upload ID returned by `/documents/upload-url`. |
curl -X POST https://corvo.to/api/v1/documents/confirm \
-H "Authorization: Bearer $CORVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"upload_id":"550e8400-e29b-41d4-a716-446655440000"}'{
"data": {
"document_key": "documents/7b2f4a1e-9c3d-4e5f-8a6b-1c2d3e4f5a6b/e1f2a3b4-5c6d-4e7f-8a9b-0c1d2e3f4a5b.pdf",
"filename": "notice.pdf",
"page_count": 48,
"size_bytes": 5242880
}
}{
"error": {
"message": "This upload has expired. Please start a new upload.",
"code": "UPLOAD_EXPIRED"
}
}