Upload and scan PDF or DOCX
Document bytes stream through the AccessPreflight API into private storage. The client never receives AWS credentials, object-store endpoints, or a presigned S3 URL.
The three-step upload contract
- Create an upload intent with expected metadata and SHA-256.
- Stream the exact bytes to the API with
Content-Digest. - Complete the upload and wait for intake status
ready.
Only then create a scan referencing the upload.
1. Hash the file
The create request uses lowercase hexadecimal SHA-256. The content request uses the same digest in RFC Content-Digest base64 form.
FILE='document.pdf'
SHA256_HEX="$(shasum -a 256 "${FILE}" | awk '{print $1}')"
SHA256_BASE64="$(openssl dgst -sha256 -binary "${FILE}" | openssl base64 -A)"
SIZE_BYTES="$(wc -c < "${FILE}" | tr -d ' ')"
2. Create an upload intent
curl --fail-with-body \
--request POST \
--url 'https://api.accesspreflight.com/v1/uploads' \
--header "Authorization: Bearer ${ACCESSPREFLIGHT_API_KEY}" \
--header 'Content-Type: application/json' \
--header "Idempotency-Key: $(uuidgen)" \
--data "{
\"project_id\": \"${ACCESSPREFLIGHT_PROJECT_ID}\",
\"environment\": \"test\",
\"file_name\": \"document.pdf\",
\"declared_media_type\": \"application/pdf\",
\"size_bytes\": ${SIZE_BYTES},
\"sha256\": \"${SHA256_HEX}\",
\"retention\": \"0h\"
}"
Supported declared media types are PDF, DOCX, HTML, and ZIP. The platform enforces the documented file, page, expansion, and package limits.
3. Stream the bytes
The body is raw bytes, not multipart form data. Content-Type, Content-Length, and the observed digest must match the intent.
curl --fail-with-body \
--request PUT \
--url "https://api.accesspreflight.com/v1/uploads/${UPLOAD_ID}/content" \
--header "Authorization: Bearer ${ACCESSPREFLIGHT_API_KEY}" \
--header "Idempotency-Key: $(uuidgen)" \
--header 'Content-Type: application/pdf' \
--header "Content-Length: ${SIZE_BYTES}" \
--header "Content-Digest: sha-256=:${SHA256_BASE64}:" \
--data-binary "@${FILE}"
Do not reconstruct Content-Length from characters or base64. It is the number of original bytes.
4. Complete and wait for intake
curl --fail-with-body \
--request POST \
--url "https://api.accesspreflight.com/v1/uploads/${UPLOAD_ID}/complete" \
--header "Authorization: Bearer ${ACCESSPREFLIGHT_API_KEY}" \
--header "Idempotency-Key: $(uuidgen)"
Completion returns 202 while intake runs. Poll GET /v1/uploads/{upload_id} until status is ready, rejected, expired, or deleted. If completion returns malware_scan_pending, honor Retry-After and retry the exact completion operation with the same idempotency key.
Intake can reject mismatched media, spoofed signatures, encrypted PDFs, macro-enabled OOXML, malformed packages, executables, archive/XML bombs, or assets above safety limits.
5. Create the scan
{
"project_id": "prj_REPLACE_ME",
"environment": "test",
"asset": {
"type": "upload",
"upload_id": "upl_REPLACE_ME"
},
"profiles": [
"pdfua-1-machine@1.2.0",
"en-301-549-v3.2.1-nonweb-document@1.2.0"
],
"quality_gate": {
"block_on": ["blocker", "critical"],
"require_manual_checklist_acknowledgement": true,
"fail_on_incomplete": true
}
}
Choose only profiles that support the detected asset type. For DOCX, page count is a structural estimate rather than a rendered page count. For PDF, the worker supplies the authoritative parsed page count after analysis.
Delete source content when no longer needed
Prefer retention: 0h when you do not need the source after processing. You can also request deletion with DELETE /v1/uploads/{upload_id}. Reports, findings, and signed evidence follow their own retention policy.