flexway
FRENTalk to an expert

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

Markdown

On this page

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"
}

Next steps