Authentication¶
Two surfaces, two auth schemes.
/v1/* — API keys¶
Public inference endpoints accept either form (the proxy checks both, in order):
| Header | Format | Notes |
|---|---|---|
Authorization |
Bearer <key> |
Standard OpenAI-style |
x-api-key |
<key> |
Anthropic-style; preferred when both are present |
Successful authentication updates last_used_at on the key row at most
once per minute (write coalescing); per-request token totals are added on
response close.
| Failure mode | Response |
|---|---|
| Missing header / invalid key | 401 {"error":"Unauthorized"} |
Key marked is_active = 0 |
401 {"error":"Unauthorized"} |
API keys are minted from the admin console (see Admin · API Keys). They are opaque, treat them as secrets, and they cannot be retrieved again after deletion.
/api/* — Session login + CSRF¶
Admin-console endpoints use a standard cookie session backed by Flask's signed-session cookie (HttpOnly, Secure, SameSite=Lax, 365 day lifetime).
Login flow.
# 1. Authenticate; receive session cookie + csrf_token in JSON body.
curl -c cookies.txt https://admin.proxy.worv.ai/api/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "<password>"}'
# {"ok": true, "username": "admin", "csrf_token": "<hex>"}
# 2. Subsequent state-changing calls (POST/PATCH/DELETE) MUST include
# X-CSRF-Token (or "_csrf" in the JSON body).
curl -b cookies.txt https://admin.proxy.worv.ai/api/keys \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <hex>" \
-d '{"name": "claude-code-laptop"}'
| Endpoint | Method | Auth |
|---|---|---|
/api/login |
POST | None — rate-limited to 10/min/IP |
/api/logout |
POST | Session + CSRF |
/api/me |
GET | Session (returns 401 if anonymous) |
GET, HEAD, OPTIONS requests are exempt from CSRF; unauthenticated
calls are also exempt (only the login itself can be unauthenticated).
POST /api/login returns 429 Too Many Requests once the per-IP limit
is exhausted (10/minute, sliding window).
Security headers¶
All responses include the following hardening headers:
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'none'
The /health path is exempt from the HTTPS-redirect middleware so that
load balancers can probe over plain HTTP.