API tokens (/api/v1)¶
What this is¶
Long-lived Bearer tokens for automating PiHerder from n8n, Home Assistant, or scripts. The browser UI uses session cookies separately.
Why it exists¶
Operators want “backup host X every night from HA” without storing a human password. Tokens are scoped, rotatable, revocable, and audited — admin only.
Where: Settings → API management (/herder-backups?tab=api) — admin only.
Catalog tab: scopes and endpoints render as a dense list (not a wide table).
Interactive OpenAPI: on your instance at /docs (Swagger — Authorize with Bearer ph_…) and /redoc (ReDoc). Those two paths allow jsDelivr so the stock FastAPI UI can load; the rest of the app stays self-hosted CSP. Settings → API also has a Try a token panel for a quick same-origin smoke check after create/rotate.
Long reference: docs/API.md in the repo.
Auth¶
- Secret shown once at create or rotate; only a hash is stored.
- Rotate issues a new secret immediately.
- Revoke soft-disables; row kept for audit.
Scopes¶
| Scope | Allows |
|---|---|
read | Catalog, health, servers, jobs GET |
jobs | POST …/servers/{id}/jobs (start work units) |
edit | PATCH …/features (feature flags) |
files | Host Files fleet list / download / upload / mkdir / rename / empty-delete — not in default scopes. Privileged, zip, edit, chmod, recursive delete stay UI + 2FA. Richer Files token API is still out of 1.4. |
feature:backup | Restrict jobs to backup-related types when any feature:* is set |
feature:os | OS patch / OS update-check jobs (apt or HAOS ha CLI when os_type=haos) |
feature:docker | Container patch / container update-check / stack check-deploy jobs |
If no feature:* scopes are set, any job type allowed by jobs may run (still subject to server feature flags). Prefer least privilege: e.g. n8n backups = read + jobs + feature:backup.
409 / exclusive jobs: a second start of the same exclusive type on a host returns HTTP 409 with the existing job_id (same rule as the UI).
IP allowlist¶
Optional IPs/CIDRs per token. Enforced using Caddy-forwarded client IP — call via ports 8888/8443.
CORS¶
Off by default. Server-side n8n/HA/curl do not need CORS. Set CORS_ORIGINS only for browser apps on other origins (exact origins; never *).
Examples¶
export PH_TOKEN='ph_…'
export PH_URL='https://piherder.example.com'
curl -sS -H "Authorization: Bearer $PH_TOKEN" "$PH_URL/api/v1" | jq .
curl -sS -H "Authorization: Bearer $PH_TOKEN" "$PH_URL/api/v1/servers" | jq .
curl -sS -X POST -H "Authorization: Bearer $PH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"job_type":"backup"}' \
"$PH_URL/api/v1/servers/1/jobs"
Prefer least privilege: e.g. n8n backup token = read + jobs + feature:backup + n8n host IP.