REST API reference
The :runtime module starts a built-in HTTP server that exposes operational, introspection, and admin endpoints. This page enumerates every endpoint: its path, method, purpose, the high-level shape of the response, and any configuration that gates it. For how-to guidance see Runtime → REST API, Health checks, and Metrics.
Server basics
The HTTP server is configured under runtime.http.* (see the Configuration reference).
| Aspect | Value |
|---|---|
| Default bind | 0.0.0.0:8080 |
| Enabled by | runtime.http.enabled (default true); the entire server — and all endpoints below — is absent when disabled |
| JSON content type | application/json; charset=utf-8 |
| Text content type | text/plain; charset=utf-8 |
| Error body | {"error":"<message>"} (JSON) for non-2xx responses |
| Wrong method | 405 Method Not Allowed with an Allow header listing the accepted method(s) |
Each endpoint accepts exactly one HTTP method. A request with any other method returns 405.
Endpoint summary
| Path | Method | Purpose | Gating |
|---|---|---|---|
/health/live | GET | Liveness probe | always |
/health/ready | GET | Readiness probe | always |
/metrics | GET | Prometheus scrape | runtime.metrics.enabled |
/info | GET | Application metadata + available endpoints | always |
/license | GET | Runtime license state | always (in-cluster exposure only) |
/config | GET | Merged config, sensitive values masked | runtime.endpoints.config.enabled |
/state | GET | Application state + transition history | always |
/topology | GET | StoatFlow-native topology | always |
/topology/ks | GET | Kafka Streams-compatible topology | always |
/topology/compiled | GET | Compiled (internal) topology | always |
/watermarks | GET | Global + per-partition watermarks | always |
/offsets | GET | Source + changelog offsets and lag | always |
/consumer | GET | Consumer group + partition buffer state | always |
/pause | POST | Pause processing | always |
/unpause | POST | Resume processing | always |
/ha/status | GET | Hot-standby cluster view | ha.mode != off |
/ha/switch | POST | Swap active/standby roles | ha.mode != off |
/ha/promote | POST | Promote a standby (?pod=<id>) | ha.mode != off |
/ha/demote | POST | Demote an active (?pod=<id>) | ha.mode != off |
/debug/threads | GET | Engine thread snapshot (freeze diagnostics) | runtime.http.debug.enabled |
/debug/barriers | GET | Commit-pipeline state (freeze diagnostics) | runtime.http.debug.enabled |
Health
GET /health/live
Kubernetes liveness probe. Aggregates all liveness indicators. Returns 200 with status: "UP" when all are healthy, otherwise 503 with status: "DOWN". Liveness stays UP during transient states (startup, shutdown) so the orchestrator does not restart the process unnecessarily.
{
"status": "UP",
"components": [
{ "name": "stoatflow", "status": "UP", "details": { "state": "RUNNING" } }
]
}
GET /health/ready
Kubernetes readiness probe. Aggregates all readiness indicators. Returns 200/UP when ready to serve, otherwise 503/DOWN. Readiness reports DOWN during startup, state restoration, and shutdown. The response body has the same shape as /health/live.
The set of indicators (StoatFlow app state, Kafka broker, license, Schema Registry when configured, plus any custom indicators) is documented under Health checks.
Metrics
GET /metrics
Prometheus scrape endpoint. Returns metrics in Prometheus text exposition format with content type text/plain; version=0.0.4; charset=utf-8. Returns 503 if metrics are not enabled.
# HELP jvm_memory_used_bytes The amount of used memory
# TYPE jvm_memory_used_bytes gauge
jvm_memory_used_bytes{application="my-app",area="heap",id="G1 Eden Space"} 1.048576E7
Gated by runtime.metrics.enabled; when disabled the handler is not registered. The exported meters are catalogued under Metrics.
Introspection
GET /info
Application metadata: application id, JVM information, Kafka configuration, uptime, current state, and the list of registered endpoints. Optional sections (watermark, offsets, consumer, topology traits) are included when available and enabled. Always returns 200 once the server is up.
{
"app": { "applicationId": "my-app" },
"java": { "version": "25", "vendor": "...", "runtime": "..." },
"kafka": { "bootstrapServers": "localhost:9092", "applicationId": "my-app" },
"uptime": { "startTime": "2026-01-19T10:00:00Z", "uptime": "PT1H30M" },
"state": { "current": "RUNNING", "since": "2026-01-19T10:00:05Z" },
"endpoints": ["/health/live", "/health/ready", "/info", "/metrics", "/topology"]
}
GET /license
Current runtime license-validation state.
licenseId, customerName, machine counts). Expose only /health/live and /health/ready via internet ingress; keep /license, /info, and /topology on the in-cluster Service.{
"status": "VALID",
"licenseId": "L-abc12-def34",
"tier": "production",
"customerName": "Acme Corp - Production",
"expiresAt": "2027-05-26T00:00:00Z",
"daysRemaining": 365,
"machinesUsed": 3,
"machinesAllowed": 10,
"lastValidatedAt": "2026-05-27T10:30:00Z",
"cachedJwtExpiresAt": "2026-05-28T10:30:00Z",
"heartbeatLastSuccessAt": "2026-05-27T10:30:00Z",
"consecutiveFailures": 0,
"graceRemainingSeconds": 604800,
"modeOnline": true
}
When no license key is present, the snapshot reports a NO_KEY status. See License configuration.
GET /config
The merged runtime configuration with sensitive values (passwords, secrets, tokens) masked. Supports content negotiation:
Accept header | Response |
|---|---|
application/json | JSON (application/json; charset=utf-8) |
text/yaml, application/yaml, application/x-yaml | YAML (text/yaml; charset=utf-8) |
text/html (browsers) or unset | YAML (default — more readable) |
stoatflow:
application-id: my-app
bootstrap-servers: localhost:9092
kafka:
consumer:
sasl.password: "******"
runtime:
http:
port: 8080
Gated by runtime.endpoints.config.enabled; when disabled the handler is not registered.
GET /state
Current application state, when it was entered, and the full state-transition history (newest first; history size is bounded by stoatflow.processing.state-transition-history-size, default 20). Returns 503 if the engine is not yet initialized.
{
"current": "RUNNING",
"since": "2026-01-19T10:00:05+01:00",
"history": [
{ "state": "RUNNING", "from": "STARTING", "at": "2026-01-19T10:00:05+01:00" },
{ "state": "STARTING", "from": "CREATED", "at": "2026-01-19T10:00:00+01:00" }
]
}
Topology
All three topology endpoints support content negotiation: Accept: application/json returns JSON; otherwise a human-readable text description. Each returns 503 if the engine is not yet initialized.
GET /topology
The StoatFlow-native topology with richer metadata than the Kafka Streams view — scheduled sources with their scheduling configuration, processor state-store names, and the full sub-topology structure.
{ "scheduledSources": [], "subtopologies": [] }
GET /topology/ks
The same topology rendered in a format compatible with Kafka Streams' Topology#describe() output, for tools that expect the standard Kafka Streams shape.
Topologies:
Sub-topology: 0
Source: source1 (topics: [input-topic])
--> processor1
Processor: processor1 (stores: [])
--> sink1
<-- source1
GET /topology/compiled
The compiled topology — the internal representation produced by the topology compiler, including per-source processor DAGs, sub-topology boundaries, timer-aware processors, and scheduled sources. Useful for verifying how the DSL was compiled into execution units. Returns 503 until the application has started (the compiled form is only available after start). The exact JSON structure is an internal representation and may change between releases; treat it as a diagnostic view, not a stable contract.
Runtime state
GET /watermarks
Global and per-partition watermark state: each partition reports its watermark (epoch ms + ISO 8601), idle flag, and last event time. Returns 503 if the engine is not initialized or no partitions are registered yet.
{
"globalMs": 1706889600000,
"globalIso": "2026-02-02T12:00:00+01:00",
"partitions": {
"input-topic:0": {
"watermarkMs": 1706889600000,
"watermarkIso": "2026-02-02T12:00:00+01:00",
"idle": false,
"lastEventTimeMs": 1706889650000,
"lastEventTimeIso": "2026-02-02T12:00:50+01:00"
}
}
}
GET /offsets
Per-partition committed offsets and lag for source topics, plus committed offsets for state-store changelog topics. All values are read from locally cached state (no Kafka RPCs). Returns 503 if the engine is not initialized.
{
"totalLag": 79,
"sources": [
{
"topic": "orders",
"totalLag": 79,
"partitions": [
{ "partition": 0, "committedOffset": 12345, "logEndOffset": 12400, "lag": 55 }
]
}
],
"changelogs": [
{
"topic": "my-app-order-counts-changelog",
"store": "order-counts",
"partitions": [ { "partition": 0, "committedOffset": 5432 } ]
}
]
}
GET /consumer
Consumer group metadata, source subscription, last commit time, and per-partition buffer utilization with pause state. Returns 503 if the engine is not initialized or the consumer has not yet been assigned partitions.
{
"group": {
"groupId": "my-app",
"memberId": "consumer-my-app-1-abc123",
"generationId": 3,
"groupInstanceId": null
},
"subscription": ["orders", "payments"],
"lastCommitMs": 1706889700000,
"lastCommitIso": "2026-02-02T12:01:40+01:00",
"assignment": {
"orders:0": { "buffered": 42, "bufferCapacity": 10000, "paused": false },
"payments:0": { "buffered": 8500, "bufferCapacity": 10000, "paused": true }
}
}
Admin
POST /pause
Pauses processing: transitions the application toward PAUSED, draining in-flight work first. Returns 503 if the engine is not initialized, or 400 if the current state cannot be paused (the error body explains why).
{ "status": "pausing", "state": "DRAINING" }
POST /unpause
Resumes processing: transitions from PAUSED / DRAINING back to RUNNING. Returns 503 if the engine is not initialized, or 400 if the current state cannot be unpaused.
{ "status": "resumed", "state": "RUNNING" }
See Pause / unpause for the operational guide.
High availability
These endpoints are registered only when ha.mode != off. They return 404 when HA is disabled and 503 before the engine is initialized. See High availability for the operational guide.
GET /ha/status
This pod's view of the hot-standby cluster: its own role, replication lag, coordinator-tail freshness, ready-standby count vs ha.desired-standbys, promotion-token epoch/holder, restart-required reason (or null), and the peers it observes.
{
"selfPodId": "my-stream-app-0",
"selfRole": "ACTIVE",
"selfState": "ACTIVE",
"replicationLagRecords": 0,
"replicationLagMs": 0,
"tailFreshnessMs": 120,
"tailCaughtUp": true,
"readyStandbyCount": 1,
"desiredStandbys": 1,
"redundancyBelowDesired": false,
"tokenEpoch": 7,
"tokenHolder": "my-stream-app-0",
"restartRequired": null,
"peers": [
{ "podId": "my-stream-app-1", "role": "STANDBY", "state": "READY_STANDBY",
"replicationLagRecords": 1240, "replicationLagMs": 85, "generation": 7,
"freshnessMs": 420, "failoverPriority": 0 }
]
}
POST /ha/switch
Swap roles — the active drains and the peer promotes. Publishes a command and returns 202 Accepted with the command's offset as an idempotency token.
{ "command": "SWITCH", "target": null, "force": false, "commandOffset": 42 }
Readiness gate: all three command endpoints are rejected with 409 Conflict unless a caught-up target exists (a node in READY_STANDBY) — handing off to a not-ready peer is a self-inflicted outage. Override with ?force=true; the override travels with the command and is re-checked on the acting pod, but a forced promotion still fully restores state before processing.
POST /ha/promote
Ask a specific standby pod to promote. Requires a ?pod=<id> target (400 if missing); 409 unless that pod is READY_STANDBY (override ?force=true). Returns 202 with the command offset.
POST /ha/demote
Ask a specific active pod to demote. Requires a ?pod=<id> target (400 if missing); 409 unless some node is READY_STANDBY to take over (override ?force=true). Returns 202 with the command offset.
Debug
The two /debug/* endpoints are diagnostic aids for investigating a stalled commit pipeline. They are gated by runtime.http.debug.enabled (default true); when disabled, neither handler is registered and both paths return 404. They are free at rest and invaluable during a freeze; disable only for hardened deployments that minimise attack surface. When enabled, both return 503 until the engine is initialized.
GET /debug/threads
A JSON snapshot of the engine's threads (and the JVM's platform threads), with each thread's state and a truncated stack trace — for answering "what is the engine stuck on?" during a freeze.
Optional query parameters:
| Param | Default | Meaning |
|---|---|---|
depth | 40 | Maximum stack frames per thread |
filter | all | all, stuck (only threads not RUNNABLE), or known (only engine-owned threads) |
GET /debug/barriers
A JSON snapshot of commit-pipeline state, designed to answer "which commit is stuck, at which phase, and what is blocking it?" from a single call. The response includes a derived phase field summarising the pipeline state at a glance (for example IN_TX_COMMIT, AWAITING_LANE_ACKS, or IDLE).
Related
- Runtime → REST API — narrative guide to the endpoints with curl examples.
- Health checks — the health-indicator system behind
/health/*. - Metrics — the meters exposed at
/metrics. - Configuration reference — the
runtime.http.*,runtime.metrics.*, andruntime.endpoints.*keys. - Kubernetes and Probes — wiring the probes and choosing which endpoints to expose.
Configuration reference
Curated reference of the StoatFlow configuration keys — stoatflow.* (engine) and runtime.* (HTTP, metrics, health) — with type, default, and a one-line description per key.
Gradle plugin reference
The io.stoatflow convention plugin — the full stoatflow { } extension DSL, the plugins and JVM flags it applies, the tasks it contributes, and the Docker and native-image defaults.