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>:8443 is 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/--insecure to curl on a trusted management network.
  • http://<appliance>:8080 is an optional plaintext listener that serves only /healthz and /metrics and answers everything else with a 308 redirect to the HTTPS port. Start openvtld with -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.

Pages