scans:create and scans:read.
Queue it
202 is work accepted, not work done. 200 means an existing scan was
returned instead, which is how a client tells a deduplicated retry from a new
scan — the two look the same in the body, so branch on the status here.
The image reference is untrusted input and is validated at the boundary.
Private and link-local registries, embedded credentials, URL schemes and
traversal segments are all refused with a message naming the rule. TRUSTIVAN
pulls remotely from the registry. A registry that requires a login needs a
credential stored against its host — see Private
registries — and the scan carries a reference to it, never
the secret itself.
Make the retry safe
Idempotency-Key is a header, or idempotency_key in the body. Use something
your pipeline already has and will reproduce on a retry — a build ID, a job
ID — not a random value.
The key is reserved before any work begins, so two concurrent retries cannot
both queue a scan. A completed request replays its stored response verbatim
with Idempotent-Replay: true. Reusing the same key with a different body
is 409, never a replay: returning the first response would tell you that work
succeeded which was never attempted. Records are kept for 30 days.
Poll to a terminal state
succeeded and failed. Anything else means keep
polling. Two seconds between attempts is reasonable; give the loop a ceiling,
because pulling and unpacking a large image takes minutes and an unbounded poll
loop is a build that hangs rather than one that reports a problem.
A retryable failure returns the scan to queued rather than reporting failed,
so a failed status is genuinely terminal.
Check database_state before you trust the result
Gotchas
- Concurrency is capped per tenant (default 4). Work over the cap stays
queued; nothing is dropped, but a burst of scans will not all start at once. findings_totalof zero on a real image is a signal, not a result. Checkdatabase_stateand the scan’s recorded scanners before believing it.- A
404on the scan means “no such scan, or not yours”. The two are byte-identical on purpose. - Secret and misconfiguration scanners are off by default. Rather than
passing them per request, set them once for a target kind with
PUT /api/v1/settings/scanners/{kind}; their findings appear in the dashboard like any other. Licences need no scanner at all — read them from/api/v1/licenses.
See also
- Idempotency — reservation, replay and the conflict rule.
- Endpoints — the full parameter set for
POST /scansandGET /scans/{id}. - Gate a pipeline on a policy — what to do with the scan once it is terminal.

