# Document scans

Upload a document with `POST /v1/scan/document`. Send `Prefer: respond-async`
to accept background work and poll for the result. Omit that header to wait for
inline processing. Both modes use the same multipart request body.

## Upload and accept work

Use an active project key with `scan:write` to upload and `result:read` to poll.
The API creates the `scan_uuid`; no separate document creation request is needed.

```sh
export ZAUN_BASE_URL='https://api.zaun.ai'
export ZAUN_API_KEY='zaun_REPLACE_WITH_YOUR_KEY'

curl --silent --show-error --fail-with-body --include \
  "$ZAUN_BASE_URL/v1/scan/document" \
  -H "Authorization: Bearer $ZAUN_API_KEY" \
  -H 'Prefer: respond-async' \
  -F 'file=@./document.pdf' \
  -F 'metadata={"source":"upload"}'
```

Let your HTTP client set `Content-Type: multipart/form-data` and its boundary.
The `file` part contains the actual bytes. Do not send a URL or base64 JSON.
There is no `async` body parameter.

| Multipart field | Required | Value |
| --- | --- | --- |
| `file` | Yes | Raw document bytes. |
| `session_uuid` | No | UUID for correlation with related scans; it does not load prior content. |
| `metadata` | No | A JSON object, subject to the [metadata limits](limits.md). |
| `sample` | No | `true` (default) or `false`. With `false` the request's text is kept but never shown to you; classification and billing are unchanged. |

TXT, Markdown, CSV, HTML, PDF, DOC, DOCX, XLS and XLSX are supported. Detection uses
the file contents. The file limit is 10,485,760 bytes (10 MiB); the entire multipart
body must fit within 11,534,336 bytes (11 MiB), including fields and boundaries.

New asynchronous acceptance returns:

```http
HTTP/1.1 202 Accepted
Location: /v1/result/7ba30cda-7c73-4e44-ad37-8829327602ad
Preference-Applied: respond-async
Retry-After: 2
Content-Type: application/json

{
  "scan_uuid": "7ba30cda-7c73-4e44-ad37-8829327602ad",
  "status": "in_progress",
  "status_url": "/v1/result/7ba30cda-7c73-4e44-ad37-8829327602ad"
}
```

A `202` confirms acceptance for background processing. Poll for the final result;
acceptance is separate from the classification outcome.

## Poll for the result

Resolve `status_url` against the same API base URL. Authenticate with a
`result:read` key from the same project.

```sh
curl --silent --show-error --fail-with-body --include \
  -H "Authorization: Bearer $ZAUN_API_KEY" \
  "$ZAUN_BASE_URL/v1/result/7ba30cda-7c73-4e44-ad37-8829327602ad"
```

While processing, polling returns HTTP `202`, `status: in_progress` and a
`job_state` of `queued` or `running`. Wait for the `Retry-After` interval before
polling again. A retry may move work from `running` back to `queued` without
changing its `scan_uuid`.

Terminal retrieval returns HTTP `200`. Inspect the result body:

| `status` | Action |
| --- | --- |
| `completed` | Evaluate `verdict`, `recommendation`, `findings` and `coverage`. Completion can report a violation. |
| `inconclusive` | Treat the document as unresolved. Partial coverage or an unknown verdict is not evidence that it is benign. Benign PDF, DOC, DOCX, XLS and XLSX documents always end here with `coverage.extraction: uncertain`, because binary extraction cannot clear a document; a detected attack in one still reports `detection_verdict: violation`. |
| `failed` | Inspect the failure and stage details. Processing ended without a usable classification. |

HTTP `200` means the result was retrieved; it does not mean the document is safe.
Use the [API reference](https://console.zaun.ai/docs?guide=openapi) for complete
response schemas and [scan concepts](concepts.md) for verdict interpretation.

## Retrieve an accepted scan

Poll the returned `status_url` to retrieve accepted work without submitting another scan.
Each POST creates a new request and returns its own `scan_uuid`.

For HTTP `429` or `503`, honor `Retry-After` when present. Invalid, unsupported or
oversized uploads require correcting the request.

## Background processing

Use asynchronous mode to submit work and retrieve the result without keeping the
upload connection open. Accepted jobs are queued for processing. Poll the returned
`status_url` and honor `Retry-After` until processing finishes. If a job fails or
expires, inspect its error before submitting a new scan.

Synchronous mode waits for processing within the request.

## Extraction evidence and deletion

Document results can include extraction geometry and OCR confidence.
Coordinates are normalized page fractions; OCR confidence is on a 0–100 scale
and is not a security confidence score. Recognized regions do not identify exact
attack spans. Extracted document text is not returned in the public result.

Temporary document bytes and extraction checkpoints are retained while durable
work is pending and are scheduled for cleanup after completion or deletion.

To delete a result or cancel pending work, use `DELETE /v1/result/{scan_uuid}`
with `result:delete` permission in the same project. Deletion prevents later
publication and schedules cleanup; it cannot undo a provider call already in flight.
