Admin API
The routes of the gateway's admin port - probes, configuration in force, simulation, journals, metrics - each with a call and its answer.
ReferenceOperationsSince version 0.1.0
On this page
Admin API: OpenAPI contractThe full contract of this API: parameters, bodies, responses and schemas.
The gateway serves its admin surface on a port of its own, :9090 by default
(FLEXWAY_ADMIN_ADDR), never on the traffic port. The full contract of this surface is published with
the documentation, as OpenAPI 3.1 (admin.openapi.yaml); this page shows how to use it.
Caution
This port must never be routable from outside: /config lists the issuers and the upstreams,
/errors every tenant's errors, /simulate describes every decision. Open it to the orchestrator,
the metrics scraper and the console only. In Kubernetes, the chart gives it a ClusterIP Service
of its own and a NetworkPolicy enabled by default.
| Route | Role | Since |
|---|---|---|
GET /healthz |
The process is alive | 0.1.0 |
GET /readyz |
Ready to serve | 0.1.0 |
GET /config |
Configuration in force, secrets masked | 0.1.0 |
GET /metrics |
Metrics in the Prometheus format | 0.2.0 |
POST /simulate |
The decision on a described request, without the upstream | 1.0.0 |
POST /simulate/mqtt |
The MQTT listener's decision on a described packet, without the broker | 1.7.0 |
GET /events |
The replica's journal of events | 1.2.0 |
GET /errors |
The replica's journal of recent errors | 1.3.0 |
GET /sparkplug/nodes |
The replica's table of Sparkplug B nodes | 1.7.0 |
GET /healthz
The process is alive - never a statement about its configuration: a process alive but not ready must be
left alone, not restarted in a loop. Always 200.
curl -s http://localhost:9090/healthz
# {"status":"ok","uptime_sec":42}GET /readyz
A configuration is loaded and served. 200 with the number of operations; 503 with the reason before
the first load and from the start of a shutdown (the replica leaves the rotation before it drains).
curl -s http://localhost:9090/readyz
# 200 {"status":"ready","operations":3}
# 503 {"status":"not ready","reason":"no snapshot loaded yet"}GET /config
Every key of flexway.yaml as loaded, secret values masked, plus what the gateway derives from it:
build (version and commit), config_version, config_digest, last_reload, catalog (the operations
served), chains (each operation's policy chain and its provenance) and, when configured, agents,
kafka, ai, a2a, mcp_federation. 503 not_ready while no configuration is loaded.
curl -s http://localhost:9090/config | jq '{build, config_digest, last_reload}'GET /metrics
The Prometheus exposition of the gateway's metrics (text/plain). Their names and dimensions are a
stable surface since 1.0.0: see Observing.
curl -s http://localhost:9090/metrics | grep '^flexway_'POST /simulate
What the gateway would do with a described request: routing, origin, authorization against the
described identity (no token is verified), whitelist, validation, policy chain - then a stop before the
upstream: no token exchanged, no bucket nor quota spent, nothing counted. method, host and path are
required; headers and identity (issuer, subject, tenant, scopes, roles) optional.
curl -s http://localhost:9090/simulate -d '{"method":"GET","host":"api.example.com","path":"/orders?status=open",
"identity":{"issuer":"customers","subject":"alice","tenant":"acme","scopes":["orders:read"]}}'200 with verdict (served or refused), status, a refusal's code, operation, upstream,
chain, the policies applied and the outbound request when served; 400 malformed_request when the
description is incomplete.
POST /simulate/mqtt
What the MQTT device listener would do with a described packet (publish, subscribe or will, a
CONNECT's will) of a described device (identity: kind token, api_key or certificate, id of a
key or of an auth.mtls client, issuer, subject, tenant, scopes, claims, certificate fields):
the operations the identity satisfies, channels with their bound values written, wildcards contained,
$share/ groups, QoS, Response Topic - then it stops before the broker: nothing dialled, no rate nor
quota spent, nothing counted. packet, topic and identity are required. 404 without an MQTT
listener.
curl -s http://localhost:9090/simulate/mqtt -d '{"packet":"publish","topic":"plant/acme/press-07/telemetry","qos":1,
"identity":{"kind":"api_key","id":"press-07"}}'200 with verdict (served or refused), a refusal's reason_code and cause, operation,
upstream, the topic or filter written toward the broker, the qos it sees and the bound client_id;
400 malformed_request when the description is incomplete.
GET /events
What happened on the replica that answers: reloads (reload), circuit breaker transitions
(breaker), shedding (shedding), refused agent chains (delegation). The last 128 events of each kind,
newest first, in memory - lost at a restart. Never a tenant nor anything a caller sent.
curl -s http://localhost:9090/events | jq '.events[0]'
# {"at":"...","kind":"reload","result":"loaded","version":"7fbb3dc21e1b"}GET /errors
The recent errors of the replica that answers, in three rings of 256 (upstream, refused, internal),
newest first, at most 20 writes per second and per kind (past that, an error is counted in its kind's
dropped). Each entry carries the operation, the upstream, the verified tenant, the status, the code,
the stage, the cause, the duration and the trace when sampled; never the path received, the headers or
the body.
curl -s http://localhost:9090/errors | jq '.errors[0]'GET /sparkplug/nodes
The table of the Sparkplug B edge nodes and host applications that cross the answering replica: state (online,
offline, moved, unknown), bdSeq, gaps, device counts (devices_online, devices_total), commands admitted
and refused; ?tenant= filters one tenant, ?tenant=&node= answers that node alone with its devices listed. The
listing never carries devices: at most max_nodes entries of bounded fields (an id is 255 bytes at most).
Bounded by observability.sparkplug, lost at a restart; 404 without an MQTT listener. Node names exist only there,
never in a metric (Sparkplug B and OPC UA PubSub JSON).
curl -s 'http://localhost:9090/sparkplug/nodes?tenant=acme' | jq '.nodes[0].state'Unknown route
Any other route answers 404 in application/problem+json (RFC 9457), like every error of this
surface:
{
"type": "https://flexway.dev/problems/not_found",
"title": "No operation is declared for this route",
"status": 404,
"detail": "No such admin route.",
"instance": "/debug/pprof/",
"code": "not_found"
}