Overview & Authentication
All endpoints are served from the backend at /api/*.
Authentication
Web Endpoints (Clerk JWT)
Most endpoints require a Clerk JWT in the Authorization header:
curl -H 'Authorization: Bearer <clerk-jwt>' https://backend.example.com/api/browser/tests
Agent Device Endpoints
Agent endpoints use an API key issued during enrollment:
curl -H 'X-Agent-Key: <api-key>' -H 'X-Agent-ID: <agent-id>' https://backend.example.com/api/agent/heartbeat
Response Format
Success
{ "success": true, "data": { ... } }
Error
{ "success": false, "error": "Error message" }
Rate Limits
| Endpoint Group | Limit |
|---|---|
| Enrollment | 5 / 15 min per IP |
| Device (heartbeat, tasks) | 100 / 15 min per agent |
| Binary download | 10 / 15 min per IP |
| Key rotation | 3 / 15 min per IP |
| Auth | 20 / 15 min per IP |
Route Groups
| Prefix | Auth | Purpose |
|---|---|---|
/api/browser/* | Clerk | Test browser |
/api/analytics/* | Clerk | Elasticsearch analytics |
/api/analytics/defender/* | Clerk | Defender analytics |
/api/agent/admin/* | Clerk | Agent management |
/api/agent/* | Agent key | Device endpoints |
/api/tests/* | Clerk | Build system, certificates |
/api/integrations/* | Clerk | Defender, alerting config |
/api/analytics/risk-acceptances/* | Clerk | Risk acceptance management |
/api/users/* | Clerk | User management, invitations |
/api/auth/* | None/Clerk | CLI device flow auth |
Route Architecture
The API is organized into domain-specific route modules, each backed by dedicated services:
Cross-Module Integration Patterns
The route modules are not isolated — several critical workflows span multiple modules:
Test Execution Pipeline
Browser Routes → Tests Routes → Agent Routes → Analytics Routes → Alerting
- Browse: User discovers tests via Browser Routes
- Build: Tests Routes compile and sign the binary
- Dispatch: Agent admin routes create tasks for target agents
- Execute: Agent device routes receive tasks and report results
- Analyze: Analytics Routes query Elasticsearch for defense metrics
- Alert: If thresholds are breached, Integration Routes trigger notifications
Agent Lifecycle
Enrollment → Heartbeat → Task Polling → Result Ingestion → Key Rotation
All managed through Agent Routes with different auth strategies per sub-route:
- Public: Enrollment and binary downloads (rate-limited, no auth)
- Agent-authenticated: Heartbeat, task fetch, result submission (API key)
- Admin: Fleet management, task creation, scheduling (Clerk JWT)
Configuration Flow
Integration Routes → Defender Routes → Analytics Routes
Defender credentials saved via Integration Routes enable the Defender sync service, which populates data queried by Defender Analytics Routes.
Security Architecture
Authentication Tiers
| Tier | Mechanism | Endpoints | Key Generator |
|---|---|---|---|
| Public | Rate limiting only | Agent downloads, enrollment | IP address |
| Agent device | X-Agent-Key + X-Agent-ID headers | Heartbeat, tasks, results | Agent ID |
| Clerk JWT | Authorization: Bearer <jwt> | All admin endpoints | User ID |
| Cron secret | CRON_SECRET header (Vercel only) | Scheduled jobs | N/A |
Input Validation
All routes validate inputs before passing to services:
- UUID format checks prevent path traversal in ID parameters
- Platform validation (
osmust bewindows,linux, ordarwin) - Payload size limits protect against oversized uploads
- Type coercion via
extractFilterParams()normalizes query string types
Response Format Details
Standard Success
{
"success": true,
"data": { ... }
}
Paginated Lists
{
"success": true,
"data": [ ... ],
"total": 142,
"page": 1,
"pageSize": 50
}
Error Responses
{
"success": false,
"error": "Human-readable error message"
}
HTTP status codes follow standard conventions: 400 for validation errors, 401 for authentication failures, 403 for authorization failures, 404 for missing resources, 429 for rate limits, and 500 for server errors.
All async route handlers are wrapped with asyncHandler(), which catches rejected promises and forwards them to the global error middleware. Throw AppError with an HTTP status code for structured error responses:
throw new AppError('Resource not found', 404);