Scan workflows

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

  1. Create an upload intent with expected metadata and SHA-256.
  2. Stream the exact bytes to the API with Content-Digest.
  3. 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.

Search documentation