HTTP API
HTTP API
Section titled “HTTP API”AetherEdge runs six HTTP services, but remote applications have one network
boundary: aether-api on port 6005. This page describes the gateway, its
service-local contracts, and the security conventions that apply across it.
The OpenAPI document generated by each running service is the sole source of truth for its paths, parameters, request and response schemas, status codes, media types, and operation-specific security requirements. Use the matching Swagger UI to explore and call those operations.
Built-in documentation
Section titled “Built-in documentation”| Service | Default boundary | Swagger UI | OpenAPI JSON |
|---|---|---|---|
aether-io |
loopback | http://127.0.0.1:6001/docs |
http://127.0.0.1:6001/openapi.json |
aether-automation |
loopback | http://127.0.0.1:6002/docs |
http://127.0.0.1:6002/openapi.json |
aether-history |
loopback | http://127.0.0.1:6004/docs |
http://127.0.0.1:6004/openapi.json |
aether-api |
remote gateway | http://<edge-host>:6005/docs |
http://<edge-host>:6005/openapi.json |
aether-uplink |
loopback | http://127.0.0.1:6006/docs |
http://127.0.0.1:6006/openapi.json |
aether-alarm |
loopback | http://127.0.0.1:6007/docs |
http://127.0.0.1:6007/openapi.json |
Swagger is opt-in through each service’s swagger-ui Cargo feature. To include
it in all six services in an installer build, use:
./scripts/build-installer.sh <version> <arch> -s rust --enable-swaggerWhen enabled, /docs and /openapi.json are public routes. Swagger does not
bypass authentication on protected operations, but its schemas and the
otherwise-internal API surface are visible. Enable it only on a trusted
commissioning or development network.
Exposure boundary
Section titled “Exposure boundary”Only aether-api is a remote-facing service. Its authenticated application
gateway exposes five fixed namespaces and forwards them only to configured
loopback services:
| Remote namespace | Service-local owner |
|---|---|
/api/v1/io/* |
aether-io |
/api/v1/automation/* |
aether-automation |
/api/v1/history/* |
aether-history |
/api/v1/uplink/* |
aether-uplink |
/api/v1/alarm/* |
aether-alarm |
The target is selected by the namespace, never by caller input. Startup validation accepts only explicit loopback HTTP origins. The gateway preserves the original signed Bearer token and the small set of documented command and conditional-request headers; it discards caller-supplied actor headers and sanitizes upstream transport errors. The direct service ports remain internal and must not be published from the device.
Generated applications and downstream product interfaces use the corresponding gateway-prefixed path on port 6005. Service-local OpenAPI remains the exact operation contract during the current consolidation stage; clients translate its internal path through the fixed namespace above. This does not make the service-local port a supported client interface. A missing operation still has to be added through the owning application boundary rather than invented by a UI, attached directly to SHM, or implemented as a storage write.
Loopback is a deployment boundary, not an identity credential. IO channel commissioning plus selected automation and alarm commands authenticate at the operation boundary, but many other local management routes in io, history, uplink, automation, and alarm still rely on host isolation. Do not infer that a direct service port is safe to expose because some of its operations declare a Bearer scheme.
The current full channel-configuration query can include protocol parameters and per-channel logging configuration. It remains compatibility debt pending a redacted, authenticated application query capability. Keep the io port on loopback and do not proxy that response to an untrusted client.
Authentication model
Section titled “Authentication model”aether-api protects its management routes with a signed access JWT. REST
clients send it only in the standard header:
Authorization: Bearer <access-token>Login, refresh-token lifecycle endpoints, service health, and—when compiled
in—the documentation routes form the public transport surface described by
the gateway OpenAPI document. Public registration is disabled unless explicitly
enabled. The required JWT_SECRET_KEY must contain at least 32 bytes and must
be managed outside source control.
The gateway WebSocket is also authenticated. Its documented ?token=...
fallback is accepted only for an actual WebSocket upgrade; REST query-string
tokens are rejected. WebSocket control writes are not supported.
The gateway requires an access JWT before forwarding any namespace request. The owning service then applies operation-specific authorization:
- io channel create, update, delete, enable, and disable require an Admin or
Engineer Bearer JWT with
io.channel.manage; - automation device actions accept a Bearer access JWT or the dedicated
AetherService <token>uplink credential; - automation rule management and manual execution require an Admin or Engineer Bearer JWT with the capability documented for the operation;
- alarm rule mutation and alert resolution require an Admin or Engineer Bearer JWT;
- forwarded identity headers and loopback reachability do not satisfy these protected command boundaries.
Governed commands
Section titled “Governed commands”Commands that can change channel configuration, device, rule, processing, or alarm state declare their risk, permission, idempotency, confirmation, and audit policy in OpenAPI. The application command boundary enforces those declarations; the HTTP handler must not write SHM or storage directly.
For a protected mutation, follow the operation schema exactly. Depending on the
operation, explicit confirmation is carried as x-aether-confirmed: true or in
the request body. Supply x-request-id when documented so retries and audit
records share a stable correlation ID. Never assume that confirmation or
identity may be forwarded in an undeclared header.
An accepted governed command includes its request_id and audit outcome in the
response described by OpenAPI. If dispatch or persistence succeeded but the
terminal audit append failed, the operation is still accepted and its audit
state is marked incomplete and non-retryable. Retain the correlation ID and do
not automatically submit the command again. Failure to record the attempted
audit fails closed before dispatch.
Device-command acceptance means the local command plane accepted the request; it is not proof that physical equipment executed it. Use feedback telemetry for closed-loop confirmation.
For channel commissioning, SQLite desired configuration and the active
protocol runtime are deliberately distinct. Existing-resource mutations may
document an optional x-aether-expected-revision compare-and-set guard. An
accepted response can report an activation-pending or degraded runtime
projection after desired state has committed; reconcile by request_id and
resulting_revision rather than automatically repeating the non-idempotent
mutation. The exact headers, receipt fields, and per-operation status codes are
defined by the io Swagger document.
Response compatibility
Section titled “Response compatibility”Most business handlers return the shared success envelope:
{ "success": true, "data": { "...": "..." }, "metadata": { "...": "..." } }metadata is omitted when empty. Health probes, service banners, WebSocket
upgrades, CSV exports, and strict Data Processing responses intentionally use
their own representations.
Error responses are still migrating and may use one of these compatibility shapes:
{ "success": false, "error": { "code": 400, "message": "..." } };{ "success": false, "message": "..." };- the versioned Data Processing
{ "error": { "code": "...", ... } }form; - the flat
AetherErrormapping witherror_code,category, andretryable.
Clients must treat the operation’s OpenAPI status and response content type as authoritative and tolerate the documented compatibility shape. Do not infer a universal error schema from another service.
Contributor contract
Section titled “Contributor contract”When a route, schema, security rule, response, or feature gate changes, update the owning service’s generated OpenAPI annotations and tests in the same change. Remote examples must use the gateway-prefixed form while local contract tests may use the owning loopback service. Do not add a second endpoint list to Markdown. Run the six-service parity check:
./scripts/check-openapi-contracts.shThis check compiles the opt-in Swagger feature and verifies each service-owned Router/OpenAPI contract. It also runs in the Rust CI workflow.