REST API reference

Every HTTP endpoint the StoatFlow runtime exposes — path, method, purpose, response shape, content negotiation, status codes, and gating.

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).

AspectValue
Default bind0.0.0.0:8080
Enabled byruntime.http.enabled (default true); the entire server — and all endpoints below — is absent when disabled
JSON content typeapplication/json; charset=utf-8
Text content typetext/plain; charset=utf-8
Error body{"error":"<message>"} (JSON) for non-2xx responses
Wrong method405 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

PathMethodPurposeGating
/health/liveGETLiveness probealways
/health/readyGETReadiness probealways
/metricsGETPrometheus scraperuntime.metrics.enabled
/infoGETApplication metadata + available endpointsalways
/licenseGETRuntime license statealways (in-cluster exposure only)
/configGETMerged config, sensitive values maskedruntime.endpoints.config.enabled
/stateGETApplication state + transition historyalways
/topologyGETStoatFlow-native topologyalways
/topology/ksGETKafka Streams-compatible topologyalways
/topology/compiledGETCompiled (internal) topologyalways
/watermarksGETGlobal + per-partition watermarksalways
/offsetsGETSource + changelog offsets and lagalways
/consumerGETConsumer group + partition buffer statealways
/pausePOSTPause processingalways
/unpausePOSTResume processingalways
/ha/statusGETHot-standby cluster viewha.mode != off
/ha/switchPOSTSwap active/standby rolesha.mode != off
/ha/promotePOSTPromote a standby (?pod=<id>)ha.mode != off
/ha/demotePOSTDemote an active (?pod=<id>)ha.mode != off
/debug/threadsGETEngine thread snapshot (freeze diagnostics)runtime.http.debug.enabled
/debug/barriersGETCommit-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.

In-cluster exposure only. The response contains customer-identifying fields (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 headerResponse
application/jsonJSON (application/json; charset=utf-8)
text/yaml, application/yaml, application/x-yamlYAML (text/yaml; charset=utf-8)
text/html (browsers) or unsetYAML (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:

ParamDefaultMeaning
depth40Maximum stack frames per thread
filterallall, 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).