# Quickstart

Use an active project key with `scan:write`. Use the prompt endpoint for messages and tool calls, or the document endpoint for files. Zaun classifies the content and applies your project controls.

## Sign up

Where self-serve signup is open, create an account in the console with an email code,
Google or GitHub. Otherwise, join the waitlist or sign in with the email your
administrator invited. Signup creates your organization, a Default project with
protective controls, and a first key. The console then opens on Home, which shows the key
once and runs two scans on it: an ordinary prompt, and the published test string
`ZAUN-TEST-VIOLATION-7f3a9c`, which is always blocked.

## Set up

Load your real key from a secret store. Keep it out of source control and logs.

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

Every scan response carries a `scan_uuid`. That one value fetches the result,
deletes it, and submits feedback.

## Scan a prompt

```sh
curl --silent --show-error --fail-with-body --max-time 50 \
  "$ZAUN_BASE_URL/v1/scan/prompt" \
  -H "Authorization: Bearer $ZAUN_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary '{"messages":[{"role":"user","content":"Explain least privilege."}]}'
```

There is no `scan_type` field. Classification assesses the submitted content and
combines findings across bounded windows for long inputs.
**Every message in `messages` is scanned, including system, developer, user, assistant and tool messages. Only include messages you want scanned.**
`session_uuid` groups related activity for later retrieval. Generate one UUID per conversation on your side and reuse it on every call; Zaun never assigns one. Messages the session has already scanned are not assessed again; each call is billed for its new tokens at the scan rate and for the session's earlier tokens, which the classifier reads back, at a tenth of it. See [scan concepts](concepts.md).

Successful prompt and document scans return:

```json
{
  "scan_uuid": "3a723044-ed79-43c9-aa03-d74603503a49",
  "completed_at": "2026-09-21T23:34:02.876876059Z",
  "violation": true,
  "action": "block",
  "session_uuid": "54d5f93d-71eb-4ed0-bd0c-a8a01afeb38d",
  "taxonomy": "mitre_atlas_tactic",
  "technique": ["Execution", "Exfiltration"],
  "usage": {"input_tokens": 5, "cached_input_tokens": 0, "output_tokens": 0},
  "containment": "stop_turn"
}
```

`violation` identifies a detection independently of its configured action. If nothing
is detected, `action` is null and `technique` is empty. A detected violation uses the
strongest configured action: `block`, then `redact`, then `alert`, then `no_action`. Scans report
the action; your application enforces it. Completion endpoints enforce it themselves.

`technique` contains all detected labels, deduplicated and sorted. `session_uuid`
echoes the optional payload field or is null. `usage` is what the scan is billed:
the **protected tokens** your content adds, reported as `usage.input_tokens`, and with a
`session_uuid` the session's earlier tokens read back as `usage.cached_input_tokens`.
`usage.output_tokens` is always zero because scans charge input only, and failed
scans are never charged. `containment` is our recommendation for the turn, session or
tool, such as `stop_turn` or `disable_tool`; see [scan concepts](concepts.md#containment).

Technical failures return an error, never a compact successful response. Partial coverage
cannot report no violation, but an established violation remains flagged. A completed
classifier abstention means no violation was identified; it returns false/null/empty.
Full evidence remains available through result retrieval. Completion response bodies
and asynchronous document acknowledgements keep their existing format.

## Scan output text and tool calls

Use `POST /v1/scan/prompt` with assistant messages. Supply text, function tool calls,
or both; tool-only messages can use `content: null`. Function arguments are a JSON
object encoded as a string. This endpoint assesses content without executing tools.

```json
{
  "messages": [{
    "role": "assistant",
    "content": "Here is the requested summary.",
    "tool_calls": [{
      "id": "call_1",
      "type": "function",
      "function": {"name": "lookup", "arguments": "{\"query\":\"example\"}"}
    }]
  }],
  "sample": true
}
```

Use a `scan:write` key. The response includes a generated `scan_uuid`. Text and tool calls share scan pricing and
organization limits.

## Scan an image

User messages can carry PNG or JPEG images as data URLs, up to four per request.
Zaun reads their text and assesses it in place with the rest of the message, so
a screenshot or photographed note carrying instructions is scanned like typed text.

```json
{
  "messages": [{
    "role": "user",
    "content": [
      {"type": "text", "text": "Summarise this screenshot."},
      {"type": "image_url", "image_url": {"url": "data:image/png;base64,iVBORw0KGgo..."}}
    ]
  }]
}
```

Each image costs $0.0075 on top of the protected tokens its text adds. Images
without text are read and charged but add nothing to assess, and purely visual
content is not assessed. See [limits](limits.md) for sizes and billing rules.

## Request flow

1. Authenticate the project key and validate the content and shared organization limits.
2. Prepare submitted text, or extract document text.
3. Classify the submitted content, using overlapping windows for long inputs.
4. Combine classification and regex findings, then apply the project’s saved actions
   with the strongest action winning.
5. Return the compact result. Your application enforces the action.

A Redact match adds `redacted_prompt` to prompt responses. Document scans
never return the full redacted document. See [Project controls](project-controls.md).
An optional payload `sample: false` hides this request's text from you, in the console and in session retrieval. Classification and billing are unchanged. Zaun still keeps a redacted copy for session context and campaign detection unless the project sets `retain_activity` to false.

## Scan a document

```sh
printf '%s' 'Explain least privilege.' > example.txt
curl --silent --show-error --fail-with-body --max-time 100 \
  "$ZAUN_BASE_URL/v1/scan/document" \
  -H "Authorization: Bearer $ZAUN_API_KEY" \
  -F 'file=@example.txt'
```

Documents return the same compact response. Upload bytes rather than a URL. See
[limits](limits.md) for supported formats, byte caps, and asynchronous submission.
This example waits for inline processing. For background processing, send
`Prefer: respond-async` with the same multipart body, then poll the returned
`status_url`. See [document scans](async-documents.md) for
availability, complete request shape, acceptance response and retry behavior.

The direct extraction pipeline reads a PDF's own text layer, falling back to OCR, and uses native Office parsers,
then classifies extracted text. Full evidence is available through result retrieval.
Truncated extraction or failed coverage cannot establish benign.
Upload bytes rather than a URL. The file limit is 10 MiB inside an 11 MiB multipart
body; see [limits](limits.md) for accepted formats and exact byte caps.

## Read or delete a result

Use the `scan_uuid` from the scan response, or the `request_uuid` from a completion
response, with a key carrying `result:read` or `result:delete` in the same project:

```sh
curl --silent --show-error --fail-with-body \
  -H "Authorization: Bearer $ZAUN_API_KEY" \
  "$ZAUN_BASE_URL/v1/result/REPLACE_WITH_SCAN_UUID"
```

`DELETE` on that URL tombstones the result. It does not erase usage accounting
or permit repeating a previously dispatched generation call.

## Python

```python
import os
import requests

base = os.environ["ZAUN_BASE_URL"]
headers = {
    "Authorization": f"Bearer {os.environ['ZAUN_API_KEY']}",
}
response = requests.post(
    f"{base}/v1/scan/prompt",
    headers=headers,
    json={"messages": [{"role": "user", "content": "Explain least privilege."}]},
    timeout=50,
)
response.raise_for_status()
result = response.json()
print(result["violation"], result["action"], result["technique"])
```

Dependency failures and unready references are API errors, distinct from an inconclusive assessment.

See [scan feedback](project-controls.md#report-an-incorrect-detection) for assessment IDs and submitting a review without changing a verdict.

Scan responses identify the retained result by `scan_uuid`; it is also returned as the
`X-Scan-Request-Id` header. Completion responses use `request_uuid` for the persisted
generation. Each HTTP request starts a new operation.
