Authentication API
Mallard Metrics supports two forms of authentication:
- Session cookies — For human dashboard users.
- API keys — For programmatic access (CI/CD, integrations, monitoring).
Dashboard Authentication
POST /api/auth/setup
Sets the admin password for the first time. Returns 409 Conflict if a password is already configured.
No authentication required.
// Request — password must be at least 8 characters
{"password": "your-secure-password"}
// Response 200 — also sets HttpOnly, SameSite=Strict cookie mm_session
{"token": "<session-token>"}
// Response 400 — password too short
{"error": "Password must be at least 8 characters"}
// Response 409 — password already configured
{"error": "Admin password already configured"}
Passwords are hashed with Argon2id before storage. The plaintext password is never persisted.
POST /api/auth/login
Authenticates with the admin password and creates a session.
No authentication required.
// Request
{"password": "your-secure-password"}
// Response 200 — sets HttpOnly, SameSite=Strict cookie mm_session
{"token": "<session-token>"}
// Response 400 — no password configured yet
{"error": "No admin password configured. Use /api/auth/setup first."}
// Response 401 — wrong password
{"error": "Invalid password"}
// Response 429 — Too Many Requests (IP locked out after max failed attempts)
// Retry-After header contains the remaining lockout seconds
{"error": "Too many failed login attempts. Try again later."}
Sessions are stored in memory and expire after session_ttl_secs (default 24 hours). Sessions are cleared on server restart.
Brute-force protection: After max_login_attempts (default 5) consecutive failures from the same IP, the IP is locked out for login_lockout_secs (default 300 seconds). A successful login clears the failure count. Configure via MALLARD_MAX_LOGIN_ATTEMPTS and MALLARD_LOGIN_LOCKOUT environment variables, or the corresponding TOML fields. Set max_login_attempts = 0 to disable.
POST /api/auth/logout
Invalidates the current session.
No authentication required. If a valid session cookie is present, it is invalidated. Otherwise the endpoint is a no-op (always returns 200).
// Response 200 — clears mm_session cookie
{"status": "logged_out"}
GET /api/auth/status
Returns the current authentication state.
// No password configured (open access mode)
{"setup_required": true, "authenticated": true}
// Password configured, not logged in
{"setup_required": false, "authenticated": false}
// Password configured, logged in
{"setup_required": false, "authenticated": true}
| Field | Type | Notes |
|---|---|---|
setup_required | boolean | true when no admin password has been set. System is in open-access mode. |
authenticated | boolean | true when the request carries a valid session or API key, or when setup_required is true. |
API Key Management
API keys are prefixed with mm_ and are SHA-256 hashed before storage. The plaintext key is only returned once at creation time.
All key management endpoints require authentication.
POST /api/keys
Creates a new API key.
// Request
{"name": "ci-pipeline", "scope": "ReadOnly"}
// Response 201
{
"key": "mm_abc123...",
"key_hash": "a1b2c3...",
"name": "ci-pipeline",
"scope": "ReadOnly"
}
The key field is the only time the plaintext key is returned. Store it securely.
Scopes:
| Value | Access |
|---|---|
ReadOnly | Read-only access to stats queries. |
Admin | Full admin access (key management, config). |
GET /api/keys
Lists all API keys (without plaintext values).
[
{
"key_hash": "a1b2c3...",
"name": "ci-pipeline",
"scope": "ReadOnly",
"created_at": "2024-01-15T10:00:00Z",
"revoked": false
}
]
DELETE /api/keys/{key_hash}
Revokes an API key by its SHA-256 hex hash.
// Response 200
{"status": "revoked"}
// Response 404 if hash not found
{"error": "Key not found"}
Using API Keys
API keys can be passed in two ways:
Authorization header (Bearer token):
curl "https://your-instance.com/api/stats/main?site_id=example.com&period=30d" \
-H "Authorization: Bearer mm_abc123..."
X-API-Key header:
curl "https://your-instance.com/api/stats/main?site_id=example.com&period=30d" \
-H "X-API-Key: mm_abc123..."
Both headers are accepted on all stats and admin endpoints. ReadOnly keys can access stats endpoints; all key management endpoints (GET /api/keys, POST /api/keys, DELETE /api/keys/{hash}) require an Admin-scoped key.