06 — API Specification
1. Conventions
- Base path:
/api/v1 (versioned; breaking changes bump the major).
- Transport: HTTPS only; HTTP/2. JSON bodies (
application/json), UTF-8.
- Auth:
Authorization: Bearer <token> (session, API key, or app token). Sessions also
supported via X-Onyx-CSRF header + cookie for browser flows. WebSocket upgrades carry the
token in the first message ({"type":"auth","token":…}) — never in the URL.
- Idempotency: mutating requests may send
Idempotency-Key; the gateway returns the
original response on replay (30 min window, keyed per user).
- Pagination: cursor-based.
?limit=50&cursor=<opaque> → {"items":[…],"next_cursor":…}.
Page-size caps: 500 for lists, 2000 for file listings (with server-side filtering).
- Filtering/sorting:
?filter[type]=image&sort=-modified_at (RFC-compliant filter syntax;
allowlisted per resource).
- Field selection:
?fields=id,name,size to trim payloads for the CLI/mobile.
- Search:
?q= with a global index where supported (files, apps, users, shares).
2. Error model
{
"error": {
"code": "share_not_found",
"message": "Share 'photos' does not exist.",
"details": { "share": "photos" },
"request_id": "req_9f2c…",
"retryable": false
}
}
- Codes are stable snake_case strings (machine-consumable), messages are human-readable and
localized by the client.
- HTTP mapping: 400
invalid_argument, 401 unauthenticated, 403 permission_denied,
404 not_found, 409 conflict (e.g., name exists, pool busy), 412 precondition_failed
(e.g., If-Match ETag mismatch), 422 validation_failed (field-level errors in
details.fields), 429 rate_limited (with Retry-After), 500 internal, 503
service_unavailable (retryable).
- Validation errors include per-field messages:
details.fields: [{name, reason}].
3. Rate limiting
Per-user, per-IP buckets at the gateway: 300 req/min interactive, 30 req/min for bulk
endpoints, WebSocket messages at 60/s. RateLimit-* headers on every response. App tokens
get their own higher allowance; misbehaving clients get 429 + backoff guidance.
4. WebSocket events (/api/v1/ws)
One authenticated socket streams onyx-bus events with a subscription message
({"type":"subscribe","topics":["snap.*","backup.*"]}). Event envelope:
{ "topic": "backup.progress", "ts": "2026-08-31T10:00:00Z", "data": { "job_id": "j_1", "pct": 42 } }
Delivery is at-least-once with sequence numbers; clients reconcile state by re-fetching on
reconnect.
5. Endpoint catalog (v1)
System
| Method |
Path |
Notes |
| GET |
/system/status |
Health, versions, uptime, pool summary |
| GET |
/system/version |
OS + service versions, update channel |
| GET |
/system/logs?service=&since= |
Unified log stream (NDJSON) |
| POST |
/system/reboot / /system/shutdown |
Require admin + confirmation token |
Pools & disks
| Method |
Path |
| GET/POST |
/pools (list/create) |
| GET/PATCH/DELETE |
/pools/{id} |
| POST |
/pools/{id}/devices (add disk) |
| POST |
/pools/{id}/devices/{dev}/replace |
| GET |
/disks (SMART + layout), /disks/{id} |
| POST |
/disks/{id}/smart (run now) |
Datasets & shares
| Method |
Path |
| GET/POST |
/datasets (create = subvolume) |
| GET/PATCH/DELETE |
/datasets/{id} (quota, options) |
| GET/POST |
/shares |
| GET/PATCH/DELETE |
/shares/{id} |
| POST |
/shares/{id}/protocols/{proto} (enable/configure SMB/NFS/…) |
| GET |
/shares/{id}/connections (connection strings) |
Files
| Method |
Path |
Notes |
| GET |
/files?path= |
List dir (metadata incl. checksum, owner, ACL, tags) |
| GET |
/files/meta?path= |
Single entry |
| PUT |
/files/content?path= |
Upload (see §6, tus resumable) |
| GET |
/files/content?path= |
Download (range requests) |
| POST |
/files/mkdir, /files/rename, /files/copy, /files/move |
Bulk paths arrays |
| POST |
/files/delete |
Soft-delete → trash subvolume, 30-day retention |
| GET |
/files/search?q= |
Global + per-dir search |
Snapshots
| Method |
Path |
| GET/POST |
/snapshots (list/create for dataset) |
| POST |
/snapshots/{id}/rollback |
| GET |
/snapshots/{id}/browse?path= |
| POST |
/snapshots/{id}/restore (file-level, target path) |
| PUT |
/datasets/{id}/snapshot-policy |
Backups
| Method |
Path |
| GET/POST |
/backup-jobs |
| GET/PATCH/DELETE |
/backup-jobs/{id} |
| POST |
/backup-jobs/{id}/run, /backup-jobs/{id}/cancel |
| GET |
/backup-jobs/{id}/history |
Apps
| Method |
Path |
| GET |
/apps (installed) |
| GET |
/app-store (catalog), /app-store/{id} |
| POST |
/apps (install: {store_id, config}) |
| GET/PATCH/DELETE |
/apps/{id} (config, uninstall) |
| POST |
/apps/{id}/start|stop|restart|update |
| GET |
/apps/{id}/logs?lines= |
| GET/POST |
/apps/{id}/ports (port mapping wizard) |
Users & access
| Method |
Path |
| GET/POST |
/users |
| GET/PATCH/DELETE |
/users/{id} (password, quota, groups, 2FA reset) |
| GET/POST |
/groups, /roles |
| GET/PUT |
/users/{id}/permissions |
| GET/POST/DELETE |
/api-keys (scoped, expiring) |
| GET/POST |
/auth/login, /auth/logout, /auth/refresh |
| GET/POST/DELETE |
/auth/webauthn, /auth/totp |
| GET |
/auth/session |
| GET/POST/DELETE |
/oidc-providers (and LDAP/AD via /directory/) |
Network & system settings
| Method |
Path |
| GET/PUT |
/network/interfaces |
| GET/PUT |
/network/firewall (nftables ruleset UI) |
| GET/POST/DELETE |
/network/forwards |
| GET/PUT |
/network/vpn (WireGuard) |
| GET/PUT |
/settings (notifications, telemetry opt-in, language, theme) |
| GET/POST |
/audit?filter= (admin) |
| POST |
/ai/chat (SSE stream), /ai/actions (dry-run tool calls) |
6. File upload/download
- Uploads: resumable via the tus protocol (
/api/v1/files/tus), with server-side
chunk assembly and pause/resume — required for large media over flaky home Wi-Fi.
- Downloads: standard GET with
Range; the gateway streams from the pool with zero-copy
sendfile and enforces per-protocol bandwidth caps set on the share.
- Integrity: every upload records the file checksum; the UI verifies on completion and
reports mismatches. Thumbnails are generated server-side (
onyx-agent sidecar) at
configurable sizes.
7. Versioning and stability
v1 is additive-only while stable; new fields are optional, never breaking.
- Deprecations: marked
Deprecated: true in OpenAPI, minimum 2 minor releases before removal.
- The OpenAPI document is published at
/api/v1/openapi.json and drives the typed SDKs and
the Prism form generator (schemas → forms), so UI forms and API validation never drift.
8. AI-assisted admin surface
POST /ai/chat streams assistant turns (SSE). The assistant is tool-constrained: it may
only invoke the same endpoints above, with two extra guardrails:
- Read-only tools by default; mutating tools require per-call user confirmation in the UI.
- Every tool call is recorded in the audit log as
ai.action with the exact request and
response.
Local mode: a bundled small LLM via llama.cpp (CPU-friendly 1–4B quantized, optional download)
with RAG over the user's own docs/FAQ. Remote mode: BYO-key for stronger models (OpenAI-
compatible endpoint). See 07-security-model.md for the threat model.