Master REST API
The master exposes a REST API on the same TLS listener used for peel enrollment (default :8443).
Base path: /api/v1
Enablement
Docs endpoints (Swagger UI + OpenAPI spec) are controlled by api.docs_enabled (default false) or the --api-docs CLI flag.
Docs are served without authentication
The Swagger UI and OpenAPI spec are served unauthenticated on the peel-facing enrollment listener. That is why they are opt-in: enable them only where exposing API documentation on that listener is acceptable (e.g., a lab or the Docker playground).
Protected API endpoints are only registered when at least one token is configured in api.tokens.
enroll:
addr: ":8443"
tls_cert: /data/auth/enroll.crt
tls_key: /data/auth/enroll.key
api:
docs_enabled: false # opt-in; served without authentication when true
tokens:
- username: ci-system
token_file: /data/auth/api-tokens/ci-system.token
- username: dashboard
token_file: /data/auth/api-tokens/dashboard.tokenAuthentication
Protected routes require:
Authorization: Bearer <token>Behavior:
- Token files are read on every request (token rotation without restart).
- Comparison uses constant-time checks.
- On success, the configured
usernameis attached to request context and logs.
Token file permissions
Token files are expected to be mode 0600. At startup, the master logs a warning for every token file that is unreadable or accessible by group/others.
Playground token
The Docker playground generates a random API token (32 random bytes, hex-encoded) at /data/auth/api-tokens/integration.token, mapped to username integration-test. Read it from the shared auth volume; it is never a predictable value.
Rate Limiting
The TLS listener applies per-IP token-bucket rate limits, split by route class:
| Routes | Burst (bucket size) | Sustained rate |
|---|---|---|
/api/v1/enroll and subpaths (unauthenticated enrollment) | 10 | 1 request / 10 s |
| All other routes (REST API, docs) | 120 | 20 requests / s |
The strict budget protects the unauthenticated enrollment endpoints; the REST API budget is sized so dispatch-and-poll clients aren't locked out. Note that /api/v1/enrollments (the admin REST route) is not an enrollment path — it gets the API budget.
Endpoints
Public (only registered when docs are enabled):
GET /api/v1/docsGET /api/v1/docs/GET /api/v1/openapi.yamlGET /api/v1/openapi.json
Protected:
POST /api/v1/jobsdispatch jobGET /api/v1/jobs/{jid}job status + returnsGET /api/v1/enrollments?state=<state|all>list enrollments (default:pending)POST /api/v1/enrollments/{id}/approveapprove enrollment
Dispatch Request
POST /api/v1/jobs
{
"target": "web-*",
"function": "test.ping",
"args": {},
"timeout": "60s",
"metadata": {
"source": "ci"
}
}Notes:
- Use either
target(resolved by master) or explicittargetslist. timeoutis Go duration format. Default is60s.- Response:
202 Acceptedwithjid, resolvedtargets, and currentstatus.
Enrollment List States
For GET /api/v1/enrollments, state accepts:
pendingapprovedrejectedissuedactiverevokedall
Logging
Each API request logs a structured completion record:
msg=http.request.completerequest_id(fromX-Request-IDor generated)method,path,statusduration_ms,bytes_outremote_ip,user_agentauth_username(for authenticated routes)jid/enrollment_idwhen applicable
Unauthorized access attempts log:
msg=http.request.unauthorized- request metadata and failure reason
OpenAPI
The OpenAPI artifacts are embedded into the binary and served directly:
- YAML:
/api/v1/openapi.yaml - JSON:
/api/v1/openapi.json
Swagger UI is served from embedded static HTML:
/api/v1/docs