REST API reference
Applies to v1.0.0. Everything the web UI does, it does through this API, and a script with the right key can do the same. This reference is derived from the daemon’s route table and handlers; where the UI adds a typed confirmation, the API takes the same confirmation as a JSON field.
Base URL and transport
https://<appliance>:8443is the API. The certificate is self-signed unless you installed your own (runbook §0.1), so scripts either pin the appliance certificate or pass-k/--insecureto curl on a trusted management network.http://<appliance>:8080is an optional plaintext listener that serves only/healthzand/metricsand answers everything else with a308redirect to the HTTPS port. Startopenvtldwith-listen ""to turn it off.- Every request and response body is JSON (
Content-Type: application/json), except the multipart update upload, the support bundle download (application/gzip), the Prometheus scrape (text/plain), and the SSE stream (text/event-stream). - Path parameters are shown in braces:
/api/cartridges/{label}.
Authentication
There are two ways to authenticate. Sessions are for people; keys are for scripts.
Session cookie. POST /api/auth/login with a username and password
sets an ovtl_session cookie (HttpOnly, Secure, SameSite=Lax, 7-day
sliding window). The UI uses this. A script can too, with a cookie jar,
but a key is the better tool.
API key. Send Authorization: Bearer ovtl_… on each request. Keys
resolve to an identity only while key authentication is enabled
(apikeys.enabled = "1" in settings; the toggle in Settings → API
access keys). While it is off, a presented key is ignored and the
attempt is logged. If a request carries both a valid session cookie and
a key, the session wins.
# Enable key auth (needs an admin session or an existing admin key)
curl -sk -X PUT https://appliance:8443/api/settings \
-H 'Content-Type: application/json' \
-b cookies.txt \
-d '{"apikeys.enabled":"1"}'
# Create a read-only key. The token is returned once and never again.
curl -sk -X POST https://appliance:8443/api/apikeys \
-H 'Content-Type: application/json' \
-b cookies.txt \
-d '{"name":"grafana","role":"readonly"}'
{"id":1,"name":"grafana","role":"readonly","token":"ovtl_kJ2…"}
# Use it
curl -sk https://appliance:8443/api/status \
-H 'Authorization: Bearer ovtl_kJ2…'
Only a SHA-256 of the token is stored. A lost token cannot be
recovered: delete the key and create a new one. Every request made with
a key is audited with actor key:<name>, and the key’s last_used_at
is updated at most once a minute.
Roles
Roles are capabilities, not resource scopes. The same two roles apply to users and to keys.
| Role | May do |
|---|---|
admin |
Everything. |
readonly |
Every GET (and HEAD), with two exceptions: GET /api/system/support-bundle is admin-only, and a read-only user may change its own password. A read-only key cannot mutate anything. |
The last enabled admin user can never be demoted, disabled, or deleted.
Public endpoints
These answer without a session or key: GET /healthz, GET /metrics,
POST /api/auth/setup, POST /api/auth/login, GET /api/auth/me, and
the static UI. /metrics is deliberately unauthenticated (gauges only);
keep it on a trusted management network or disable the plaintext
listener.
First-run gate
Until the first admin exists, every authenticated route answers
409 {"error":"setup required","setup_required":true}. Create the admin
with POST /api/auth/setup (or in the browser).
Confirmations
Operations that erase data or drop host sessions require a typed
confirmation in the body, exactly as the UI asks for it. The endpoint
pages state each one. A missing or wrong confirmation is a 400 with an
error message that names the expected value; nothing happens.
Errors
Every error is a JSON object with an error string. Some carry extra
fields (setup_required, steps, created).
| Status | Meaning |
|---|---|
400 |
Validation failed, a confirmation was missing, or a preflight refused (active jobs, a drive still loaded, a pool still paired). The message says which. |
401 |
No session and no accepted key. |
403 |
Role too low, setup already completed, or a non-admin touching another user’s password. |
404 |
Unknown cartridge, job, user, key, pool, library, or initiator. |
409 |
Setup required; a label collision on import; or a graceful restart is draining and new job-creating calls are refused until it fires. |
500 |
The daemon hit an error running a system command or the database. The message carries the underlying error. |
Successful mutations return 200 or 201. Long-running work that is
handed to another process (updates, rollback, a graceful restart with
jobs active) returns 202 and describes where to watch.
Audit
Every mutation lands in the audit log with the actor (username or
key:<name>), the caller’s address, the action, its subject, and its
parameters. GET /api/audit reads the last 200 entries. Secrets never
appear in the audit log or in any response: S3 secret keys, password
hashes, key hashes, and session tokens go in and do not come back.
Live updates
GET /api/events is a Server-Sent Events stream of state changes
(job progress, cartridge moves, drive activity, pool statistics,
maintenance-window steps). A client resynchronises from GET /api/status
on connect and applies the stream on top. Details on the
events page.
Versioning
The API is versioned with the appliance, not separately. GET /healthz
and GET /api/system report the running version string (the release
tag, v1.0.0 for this reference). Routes, fields, and confirmation
strings can change between releases. Pin a script to the version it was
tested against, read the release notes before applying an update, and
re-run the script against a non-production appliance after one.