Introduction
The Router Auditor API runs 60 security detectors against an LLM API router endpoint, probing for model substitution, parameter fraud, billing manipulation, supply-chain attacks, and more.
Base URL:
https://safe.bua.sh/api/v1
All responses are JSON. The API follows REST conventions with standard HTTP status codes.
Authentication
All endpoints except /health and /detectors require a Bearer token.
Authorization: Bearer YOUR_API_KEY
AUDITOR_API_KEY environment variable on the server.
Error Handling
Errors return a JSON body with a detail field:
{
"detail": "Not found"
}
| Status | Meaning |
|---|---|
| 400 | Bad request (invalid parameters, unknown detector IDs) |
| 401 | Missing or invalid API key |
| 404 | Task not found |
| 409 | Conflict (cancel non-running, delete running task) |
| 503 | Server at capacity (max 3 concurrent tasks) |
Create Test
Queue a new audit run. Returns immediately with a task_id and WebSocket URL for real-time progress.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| router_endpoint | string | * | Base URL of the router under test |
| api_key | string | * | API key for the router |
| claimed_model | string | Model name claimed by router. Default: "gpt-4o" | |
| claimed_provider | string | openai | anthropic | gemini | any. Default: "any" | |
| capabilities | string[] | text vision pdf audio tool_calling task_model. Default: ["text"] | |
| auth_method | string | bearer | x-api-key | query. Default: "bearer" | |
| api_format | string | openai | anthropic | auto. Default: "openai" | |
| timeout | number | Per-request timeout in seconds (5–120). Default: 30 | |
| only | string[] | Run only these detector IDs, e.g. ["D4a", "D11"] | |
| direct_endpoint | string | Direct provider URL for baseline comparison | |
| direct_api_key | string | API key for direct provider | |
| callback_url | string | Webhook URL called on task completion | |
| extra_headers | object | Additional headers to send with each probe |
Response 201
{
"task_id": "a1b2c3d4e5f6",
"status": "pending",
"message": "Test created and queued",
"ws_url": "/api/v1/tests/a1b2c3d4e5f6/ws"
}
Example
curl -X POST https://safe.bua.sh/api/v1/tests \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"router_endpoint": "https://api.example.com/v1",
"api_key": "sk-router-key",
"claimed_model": "claude-opus-4-6",
"claimed_provider": "anthropic",
"capabilities": ["text", "tool_calling"],
"timeout": 60
}'
List Tests
List all audit tasks, with optional filtering.
Query Parameters
| Param | Type | Description |
|---|---|---|
| limit | int | Max results. Default: 20 |
| offset | int | Skip N results. Default: 0 |
| status | string | Filter: pending | running | completed | failed | cancelled |
| endpoint | string | Filter by router endpoint (substring match) |
Response 200
[
{
"task_id": "a1b2c3d4e5f6",
"status": "completed",
"created_at": "2026-04-15T10:30:00Z",
"completed_at": "2026-04-15T10:32:15Z",
"router_endpoint": "https://api.example.com/v1",
"claimed_model": "claude-opus-4-6",
"tier_assignment": "BLACKLIST",
"overall_verdict": "fail",
"progress": "60/60"
}
]
Get Test Detail
Get full details of a test including sanitized config and complete report.
Response 200
Returns TaskDetail — extends TaskSummary with config (API keys masked), report (full JSON), and error (if failed).
Download Report
Download the full JSON report as a file. Only available for completed tasks.
Response 200
Returns application/json with Content-Disposition: attachment.
{
"router_endpoint": "...",
"overall_verdict": "fail",
"tier_assignment": "BLACKLIST",
"total_detectors": 60,
"passed": 23,
"failed": 15,
"results": [
{
"detector_id": "D31",
"verdict": "fail",
"confidence": 1.0,
"evidence": { ... },
"latency_ms": 8324
}
]
}
Download JUnit XML
Download the report as JUnit XML for CI/CD integration. Only available for completed tasks.
Cancel Test
Cancel a running test. Returns 409 if the task is not currently running.
Delete Test
Delete a test and its results. Returns 409 if still running (cancel first).
WebSocket Progress Stream
Real-time bidirectional stream of audit progress. No authentication required (task_id acts as capability token).
Event Types
| Type | When | Data |
|---|---|---|
| status | On connect | { status, progress } |
| stage_start | Stage begins | { name, count } |
| detector_start | Detector begins | { id, name } |
| detector_end | Detector finishes | { id, verdict, latency_ms } |
| stage_end | Stage ends | { name, results[] } |
| task_end | Task complete | { status, tier } |
| ping | Every 30s | Keepalive |
Example
const ws = new WebSocket("wss://safe.bua.sh/api/v1/tests/abc123/ws");
ws.onmessage = (e) => {
const event = JSON.parse(e.data);
if (event.type === "detector_end") {
console.log(`${event.data.id}: ${event.data.verdict} (${event.data.latency_ms}ms)`);
}
if (event.type === "task_end") {
console.log(`Done: ${event.data.tier}`);
ws.close();
}
};
List Detectors
List all registered detectors with metadata. No authentication required.
Response 200
[
{
"detector_id": "D4a",
"detector_name": "TokenizerFingerprint",
"priority": "P0",
"judge_mode": "once",
"request_count": 1,
"required_capabilities": ["text"],
"required_provider": "any",
"requires_direct": false,
"requires_single_route_claim": false,
"description": "Detect model substitution via tokenizer..."
}
]
Health Check
Health check endpoint. No authentication required.
Response 200
{
"status": "ok",
"version": "0.1.0",
"active_tasks": 1,
"total_completed": 42
}
Quick Start
Run a full audit in 3 steps:
1. Create a test
TASK=$(curl -s -X POST https://safe.bua.sh/api/v1/tests \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"router_endpoint": "https://api.router.com/v1",
"api_key": "sk-router-key",
"claimed_model": "gpt-4o",
"claimed_provider": "openai"
}' | jq -r .task_id)
echo "Task ID: $TASK"
2. Poll for completion
while true; do
STATUS=$(curl -s https://safe.bua.sh/api/v1/tests/$TASK \
-H "Authorization: Bearer $API_KEY" | jq -r .status)
echo "Status: $STATUS"
[ "$STATUS" = "completed" ] && break
sleep 5
done
3. Download the report
curl -s https://safe.bua.sh/api/v1/tests/$TASK/report \
-H "Authorization: Bearer $API_KEY" | jq .tier_assignment
# → "BLACKLIST" or "TIER_1"
Webhooks
If callback_url is provided when creating a test, the server sends a POST request on completion:
POST https://your-server.com/webhook
Content-Type: application/json
{
"task_id": "a1b2c3d4e5f6",
"status": "completed",
"overall_verdict": "fail",
"tier_assignment": "BLACKLIST",
"passed": 23,
"failed": 15,
"report_url": "/api/v1/tests/a1b2c3d4e5f6/report"
}
Webhook timeout is 10 seconds. Failures are logged but do not affect the task result.
WebSocket Guide
For real-time progress tracking, connect to the WebSocket endpoint immediately after creating a test:
Python Example
import asyncio, json, httpx, websockets
async def audit(endpoint, api_key):
async with httpx.AsyncClient() as client:
r = await client.post(
"https://safe.bua.sh/api/v1/tests",
headers={"Authorization": "Bearer YOUR_AUDITOR_KEY"},
json={
"router_endpoint": endpoint,
"api_key": api_key,
"claimed_model": "gpt-4o",
},
)
task_id = r.json()["task_id"]
async with websockets.connect(
f"wss://safe.bua.sh/api/v1/tests/{task_id}/ws"
) as ws:
async for msg in ws:
ev = json.loads(msg)
if ev["type"] == "detector_end":
d = ev["data"]
print(f" {d['id']}: {d['verdict']}")
if ev["type"] == "task_end":
print(f"Result: {ev['data']['tier']}")
break
asyncio.run(audit("https://api.router.com/v1", "sk-key"))
Connection Lifecycle
Client Server
│ │
├── connect ──────────────────► │
│ ├── status {pending}
│ ├── stage_start {pre_screen}
│ ├── detector_start {D31}
│ ├── detector_end {D31, fail}
│ ├── stage_end {pre_screen}
│ ├── stage_start {s0}
│ ├── ...
│ ├── ping (every 30s)
│ ├── ...
│ ├── task_end {completed, BLACKLIST}
│ ◄─────────────────── close ──┤
│ │
Router Auditor v2.0 — 85 detectors — Back to app