Build for an asynchronous scan lifecycle
Creating a scan reserves work and returns quickly. Rendering, document parsing, engine execution, normalization, reporting, and delivery happen asynchronously.
State model
The common successful path is:
queued → fetching → scanning → normalizing → reporting → completed
The API can also return early setup states such as created or awaiting_upload. Terminal non-success states are:
| Status | Meaning | Typical client action |
|---|---|---|
rejected |
Intake or policy rejected the source | Fix the request or source; do not retry unchanged |
fetch_failed |
A URL or required source could not be retrieved | Read the safe diagnostic; retry only transient causes |
unsupported |
The source or requested profile combination is unsupported | Select a compatible source/profile |
timed_out |
Work exceeded a bounded execution limit | Simplify readiness conditions or retry later once |
engine_failed |
A required scanner could not produce a trustworthy result | Retry with backoff; contact support if persistent |
cancelled |
Cancellation was accepted before completion | Create a new scan to run again |
completed is the only terminal state with a final accessibility result and immutable report set.
Poll without creating load spikes
Start around two seconds, increase toward ten seconds, and apply jitter. Use a client deadline longer than the normal scan SLO but shorter than an unbounded background process.
2s, 3s, 5s, 8s, 10s, 10s … plus random jitter
Honor Retry-After whenever the API supplies it. Stop polling immediately after any terminal status.
Do not automatically retry POST /v1/scans with a new idempotency key after a network interruption. First replay the same request with the same key; the API will return the originally accepted scan if it committed.
Prefer webhooks plus reconciliation
Subscribe to the supported scan event types for low-latency completion. A webhook is an at-least-once notification, not your source of truth. Fetch the scan through its authenticated API link before making a release decision.
Recommended pattern:
- Create the scan and persist its ID.
- Receive and verify a webhook.
- Deduplicate by webhook event ID.
- Fetch the current scan from
links.self. - Periodically reconcile scans that remain non-terminal after your expected window.
Cancellation
Use POST /v1/scans/{scan_id}/cancel with a new idempotency key. Cancellation is cooperative: a worker may finish before it observes the cancellation. The returned scan object is authoritative.
{
"reason": "Deployment superseded by commit 9d31a44"
}
Keep the reason free of secrets and personal data. It becomes sanitized operational and audit context.
Retries are not all equivalent
- Retry
429and transient503usingRetry-Afteror exponential backoff. - Replay create operations only with the same idempotency key and byte-identical body.
- Do not retry validation, unsupported, unsafe-egress, encrypted-file, or malformed-package failures unchanged.
- Do not infer retryability from the English message; use the HTTP status and stable error/failure code.
Store X-Request-ID with every failed operation. It is the support reference that connects a customer observation to redacted platform logs.