Docs / run-adversarial-scan

Run an adversarial scan

← All docs

Adversarial scans send a curated set of probes from the Penaxtra probe library to a registered LLM endpoint, score each response with three independent judges plus a meta-judge, and persist findings mapped to six compliance frameworks.

POST /api/v2/scans

curl -sS -X POST https://penaxtra.com/api/v2/scans \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "endpoint_id": "<uuid>",
    "probe_ids": ["prompt-injection-basic", "jailbreak-roleplay"]
  }'

Required scope: scans:write. probe_ids is optional; omit the field (or send an empty array) to run every probe in the library.

Response

{
  "accepted": true,
  "scan_id": "<uuid>",
  "endpoint_id": "<uuid>",
  "probe_count": 2,
  "poll": "/api/v2/scans/<uuid>"
}

probe_count is null when every probe is selected. scan_id is durable; persist it before retrying so a partial outage does not produce duplicate runs.

GET /api/v2/scans/{id}

curl -sS https://penaxtra.com/api/v2/scans/<uuid> \
  -H "Authorization: Bearer $TOKEN"

status transitions through queued -> running -> judging -> completed (or failed). The same row is also returned by the list endpoint at /api/v2/scans.

GET /api/v2/findings (filtered by scan)

curl -sS "https://penaxtra.com/api/v2/findings?severity=high" \
  -H "Authorization: Bearer $TOKEN"

See the Findings API for the full filter set and the per-finding GET for the detail payload.

Errors

CodeBodyReason
400{"error":"invalid_endpoint_id"}endpoint_id missing or not a UUID.
400{"error":"invalid_probe_ids"}probe_ids is not an array of slug strings. Slug regex: ^[a-z][a-z0-9-]{2,79}$.
400{"error":"too_many_probe_ids"}More than 100 probe ids per request; trim the list.
404{"error":"endpoint_not_found_or_inactive"}The endpoint id does not belong to the caller's tenant or is not in active status.
413{"error":"payload_too_large","limit_bytes":1048576}Request body exceeded the 1 MiB cap.
429{"error":"rate_limited","retry_after_seconds":N}Tighter cap of six triggers per minute per tenant. The Retry-After header carries the same number.
502{"error":"enqueue_failed","scan_id":"<uuid>"}The internal scan worker rejected the enqueue request; the scan row is marked failed. Retry with a fresh request after a short backoff.

Audit

Every POST is recorded as an api.call row carrying the calling token id, method, path, response status, and duration. Triggered scans also produce a scan.started row (or scan.enqueue_failed on the unhappy path).

Security notes

  • The endpoint must be active to accept new scans. Disabled or pending endpoints return 404 by design so an attacker cannot enumerate inactive endpoint ids.
  • Probe ids are an enumerable static set; supply a curated list when possible to keep cost predictable.
  • The scan row is created before the enqueue call, so a 502 leaves the row addressable for retry diagnosis.

Related

Last reviewed: 2026-06-13. Reviewed by: Engineering. Content type: Developer documentation. Reach the maintainers: [email protected] .