# 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](permissions.md) has what each call needs. ## Errors ```json {"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: ```json {"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 | | `GET /catalog` | the option catalog, value types, reserved codes, roles | ### Settings `GET /settings`, `PATCH /settings` with any of: ```json {"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](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 | ```json {"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", "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](kea.md)); answers with `leases`, `notes`, `skipped` too | ### 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=]` | |