Scan workflows

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:

  1. Create the scan and persist its ID.
  2. Receive and verify a webhook.
  3. Deduplicate by webhook event ID.
  4. Fetch the current scan from links.self.
  5. 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 429 and transient 503 using Retry-After or 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.

Search documentation