Files
steward/docs/api.md
T
abrown38andClaude Opus 5.5 8ee430276c Address order: sequential, reverse or random, per subnet and pool
- Which never-used pool address a new client gets: sequential (the
  lowest, as before), reverse or random, in the settings, a subnet or
  a single pool (FIRST-LAST:ORDER). When no never-used address is left,
  the one free the longest, whatever the order; returning clients keep
  theirs.
- A subnet's pools are used in the order listed, or one at random
  weighted by its never-used addresses. Pools now keep the order they
  are given in (no more sorting by address).
- CLI --address-order and --pool-order; --add-pool of an existing range
  changes its order in place. Web UI: per-pool order and a move-up
  button, pool and address order on the subnet and settings pages.
- Kea's allocator: random is imported. Schema migration 5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 18:13:25 +01:00

7.4 KiB

The REST API

Everything the web UI and the CLI do goes through this API, under /api/v1. Requests and answers are JSON.

Authentication

  • API tokens: Authorization: Bearer steward_.... Make one in the web UI (your account), with steward token create NAME, or with stewardd user token USER NAME on the server.
  • Sessions: POST /auth/login with {"username", "password"} sets a cookie and returns csrf_token. Requests other than GET must send it as X-CSRF-Token.
  • The local socket (/run/steward/steward.sock): root and stewardd's own user need nothing; others send a token.

Failed logins and token attempts are rate limited per address and per user name. permissions.md has what each call needs.

Errors

{"error": {"code": "validation", "message": "subnet iot (10.0.20.0/24): it overlaps subnet office (10.0.10.0/24)",
           "problems": [{"severity": "error", "code": "subnet_overlap", "message": "...", "object": "subnet iot (10.0.20.0/24)"}]}}
Status Code
400 bad_request the request can't be read (an unknown option name, a bad address)
401 unauthenticated, session_expired, login_failed
403 forbidden, csrf
404 not_found
409 conflict e.g. undoing a change after the objects changed again
422 validation the change would make the configuration invalid; problems lists why
429 rate_limited

Changes

Calls that change the configuration answer:

{"applied": true, "history_id": 7,
 "changes": [{"kind": "subnet", "id": 12, "label": "iot (10.0.20.0/24)", "before": {...}, "after": {...}}],
 "warnings": [{"severity": "warning", "code": "interface_unknown", "message": "...", "object": "..."}],
 "object": {...}}
  • ?dry_run=1 checks the change and returns what it would do, with "applied": false, "dry_run": true.
  • before is missing for a create and after for a delete.
  • warnings are the problems the change brings; older ones are in /status.
  • object is the created or changed object.

Creating answers 201, other changes 200.

Endpoints

Status

GET /healthz {"ok": true, "version"}, without authentication
GET /status build, start time, interfaces (listening, error, addresses, subnets), subnets with usage, problems, leases (counts by state), messages (counts by type), ping_problem
GET /interfaces the host's interfaces, their addresses and subnets, leaving out those matching interfaces.exclude
GET /catalog the option catalog, value types, reserved codes, roles

Settings

GET /settings, PATCH /settings with any of:

{"lease_time": 43200, "ping_check": true, "ping_timeout_ms": 500, "decline_hold": 86400,
 "not_authoritative": false, "address_order": "sequential",
 "options": [{"option": "domain-name-servers", "value": "10.0.10.2"}]}

address_order is sequential (left out when it is), reverse or random (subnets.md).

options replaces the list. Each option is {"option": NAME or NUMBER, "value": TEXT}, with "type" for numbers the catalog doesn't know (options.md).

Subnets

GET /subnets every subnet, with usage (pool_size, leased, declined, reservations, reservations_leased)
POST /subnets create; needs prefix and interface
GET /subnets/{ref} by ID, name, or prefix with _ for / (10.0.10.0_24)
PATCH /subnets/{ref} change the given fields
DELETE /subnets/{ref} delete, with its reservations
{"name": "office", "interface": "eth0", "prefix": "10.0.10.0/24",
 "pools": ["10.0.10.100-10.0.10.199", "10.0.10.224/28:random"], "pool_order": "listed",
 "address_order": "default", "lease_time": 0,
 "options": [{"option": "routers", "value": "10.0.10.1"}], "disabled": false, "comment": ""}

pools and options replace the lists; the pools keep their order. A pool's :ORDER is its own address order. pool_order is listed or random; address_order is sequential, reverse, random or default (the settings'). Subnets come back with pools as {"start", "end", "order"}, and the defaults (listed, default) left out. lease_time 0 means the settings'.

Reservations

GET /reservations[?subnet=REF] each with subnet_id, subnet and its current lease
POST /reservations {"ip", "mac", "client_id", "hostname", "options", "disabled", "comment"}; ip and mac or client_id needed
GET, PATCH, DELETE /reservations/{ref} by ID, address, or MAC address (when it has one reservation)

Leases and activity

GET /leases[?subnet=&state=&interface=&q=] every lease, with its state now (active, expired, released, declined), subnet and its reservation, and last_seen when the client last answered a ping (left out if it never has); q searches address, MAC and host name
GET /leases/{ip} one
DELETE /leases/{ip} forget it (audited)
POST /leases/{ip}/reserve make it a reservation; optional {"hostname", "comment"}
GET /events[?mac=&subnet=&interface=&limit=] the latest DHCP messages, newest first (the last 1000 are kept in memory). subnet (ID, name or prefix) keeps messages for its addresses, and those without an address on its interface

History and import

GET /history[?limit=&before=] changes, newest first
GET /history/{id} one, with each object before and after
POST /history/{id}/revert undo it; 409 if an object it touched has changed since, or it was undone already
POST /import/kea {"config": TEXT, "leases": [TEXT, ...], "interfaces": {"PREFIX": "IFACE"}, "replace": false} (kea.md); answers with leases, notes, skipped too

Certificate

Administrators only (certificates.md).

GET /certificate {"configured", "source": "acme"|"file"|"none", "status": {"domain", "directory", "staging", "state": "none"|"ok"|"working"|"error", "not_before", "not_after", "issuer", "renew_at", "last_error", "last_attempt", "next_attempt", "check"}}
POST /certificate/check run the check now (up to a few minutes); answers {"time", "domain", "ok", "steps": [{"name", "status", "detail", "queries"}]}
POST /certificate/renew get a new certificate now, in the background (202); follow it with GET /certificate

GET /status lists the certificate's problems (certificate_failed, certificate_expiring, certificate_staging) for administrators.

Users, tokens, audit

POST /auth/login, POST /auth/logout, GET /auth/whoami whoami has token_id when the caller uses a token
POST /me/password {"current", "password"}
GET /me/tokens, POST /me/tokens {"name", "role", "grants", "expires_in"}; the answer's secret is shown once
GET /tokens, DELETE /tokens/{id} all tokens (administrators), revoke
POST /tokens/{id}/rotate a new secret: {"grace": "1h", "expires_in": "90d", "no_expiry": false}, all optional; answers with secret and previous_secret_valid_until
GET, POST /users; PATCH, DELETE /users/{id} administrators; {"username", "password", "admin", "role", "grants", "disabled", "comment"}
GET, POST /users/{id}/tokens
GET /audit[?user=&action=&result=&q=&before=&limit=]