release / release (push) Successful in 5m53s
- A disabled reservation is kept but ignored: the client gets a pool address like any other (steward reservation disable|enable, buttons and a checkbox in the web UI). Schema migration 3. - Subnets get Disable/Enable buttons on their page and in the list. - A rule left without its selector in style.css broke the page heading's layout, so the New ... buttons sat cramped under the description. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6.6 KiB
6.6 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), withsteward token create NAME, or withstewardd user token USER NAMEon the server. - Sessions:
POST /auth/loginwith{"username", "password"}sets a cookie and returnscsrf_token. Requests other than GET must send it asX-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=1checks the change and returns what it would do, with"applied": false, "dry_run": true.beforeis missing for a create andafterfor a delete.warningsare the problems the change brings; older ones are in/status.objectis 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 |
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, "options": [{"option": "domain-name-servers", "value": "10.0.10.2"}]}
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"], "lease_time": 0,
"options": [{"option": "routers", "value": "10.0.10.1"}], "disabled": false, "comment": ""}
pools and options replace the lists; 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; 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=&interface=&limit=] |
the latest DHCP messages, newest first (the last 1000 are kept in memory) |
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=] |