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
| Code | Body | Reason |
|---|---|---|
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
activeto 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] .