REST API
Ironflow exposes a REST API for managing functions, emitting events, querying runs, and more.
Base URL: http://localhost:9123/api/v1
Authentication
Section titled “Authentication”All API endpoints (except /health, /ready, /metrics, /api/v1/capabilities, /api/v1/auth/login, /api/v1/auth/validate, and /api/v1/platform/auth/login) require authentication.
API Key (SDK and programmatic access):
Authorization: Bearer ifkey_your_key_hereDashboard JWT (browser sessions):
Dashboard authentication uses the ironflow_dashboard_token cookie set by the login endpoint.
Environment Selection (optional):
X-Ironflow-Environment: env_productionEvents
Section titled “Events”POST /events
Section titled “POST /events”Emit a user event to trigger matching workflows.
Request:
{ "name": "order.placed", "data": { "orderId": "123", "total": 99.99 }, "metadata": { "source": "checkout", "traceId": "abc-123" }}The metadata field is optional. It accepts any key-value pairs and is delivered to function handlers as event.metadata (TypeScript) or ctx.Event.Metadata (Go). Use it to pass tracing IDs, source labels, or any contextual information that shouldn’t be part of the event’s core data schema.
Response:
{ "event_id": "evt_abc123", "run_ids": ["run_xyz789"]}GET /events
Section titled “GET /events”List events with keyset pagination (issue #697). Sorted newest-first by
(timestamp, id). The endpoint always scopes to the caller’s environment.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
name | string | Filter by event name |
source | string | Filter by source (comma-separated) |
since | string | Start time (RFC3339) |
until | string | End time (RFC3339) |
limit | int | Max results per page (default 20) |
cursor | string | Forward keyset token from a prior next_cursor. Mutex with before. |
before | string | Backward keyset token from a prior prev_cursor. Mutex with cursor. |
Response:
{ "events": [...], "count": 20, "limit": 20, "next_cursor": "MTcxMjMzNDQ1Njc4OXxldnRfMDFI...", "prev_cursor": "MTcxMjMzNDQ1NjAwMHxldnRfMDFI...", "has_next": true, "has_prev": false, "approx_total": 12400, "approx_total_capped": false}next_cursor/prev_cursorarenull/omitted when there is no further page in that direction.has_nextandhas_prevare direction-aware. On the newest page (no cursor and nobefore),has_previs alwaysfalse. After paging forward withcursor=,has_previstrue. After paging backward withbefore=,has_nextistrue.approx_totalis the per-environment count from a capped subquery (LIMIT 10001). Below the cap the value is exact; above the cap the value is10000andapprox_total_cappedistrue.
Errors:
400— invalidcursor/beforetoken, or both supplied at once.
GET /events/{id}
Section titled “GET /events/{id}”Get a single event by ID.
Functions
Section titled “Functions”Function registration (POST /ironflow.v1.IronflowService/RegisterFunction) accepts an optional change_reason field (e.g. "deploy v2.1.0") that is recorded in the function’s history for every update. Has no effect on new registrations (no history event is written on first create).
GET /functions
Section titled “GET /functions”List all registered functions.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
name | string | Filter by function name |
status | string | Filter by status |
mode | string | Filter by execution mode |
limit | int | Max results (returns all if omitted) |
offset | int | Pagination offset |
POST /functions/{id}/invoke
Section titled “POST /functions/{id}/invoke”Directly invoke a function, creating an event and run.
Request:
{ "data": { "orderId": "123" }}Headers:
| Header | Description |
|---|---|
Idempotency-Key | Optional. When supplied, a retry with the same key + same function_id in the same environment returns the original run_id + event_id instead of creating a duplicate run. Reusing a key for a different function, or for a key already used by POST /events, returns 409 Conflict — keys are scoped per (environment, key) and not shareable across flows. |
Response:
{ "run_id": "run_abc", "event_id": "evt_xyz"}Function History
Section titled “Function History”Function registration history is exposed via the ConnectRPC API. Every update, status change, rollback, and deletion is recorded as an immutable snapshot against ironflow:fn:{id} in the entity stream.
See Function Registration History for a full explanation of the flows.
List Function History
Section titled “List Function History”POST /ironflow.v1.IronflowService/ListFunctionHistoryRequest:
{ "function_id": "my-function", "limit": 20, "from_version": 0}Use from_version (exclusive) for keyset pagination — pass the entity_version of the last entry to get the next page.
Response:
{ "entries": [ { "event_id": "evt_abc", "entity_version": 3, "function_id": "my-function", "function_snapshot": { ... }, "actor_id": "ifkey_xyz", "change_reason": "deploy v2.1.0", "change_type": "update", "recorded_at": "2026-04-12T10:00:00Z" } ], "has_more": false}change_type values: update, status_change, rollback, delete.
Get Function at Version
Section titled “Get Function at Version”POST /ironflow.v1.IronflowService/GetFunctionAtVersionRequest:
{ "function_id": "my-function", "version": 2}Response:
{ "entry": { "event_id": "evt_abc", "entity_version": 2, "function_snapshot": { ... }, "actor_id": "ifkey_xyz", "change_type": "update", "recorded_at": "2026-04-10T08:00:00Z" }}Rollback Function
Section titled “Rollback Function”POST /ironflow.v1.IronflowService/RollbackFunctionRestores a function to the configuration captured at version. The rollback is itself recorded as a new history entry with change_type = "rollback". Returns CodeAborted on concurrent modification — retry in that case.
Request:
{ "function_id": "my-function", "version": 2, "change_reason": "revert bad deploy"}Response:
{ "function": { ... }}Cannot roll back to an archived state (CodeInvalidArgument).
GET /runs
Section titled “GET /runs”List workflow runs with optional filters.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
function_id | string | Filter by function |
status | string | Filter by status (comma-separated: pending, running, completed, failed, cancelled, paused) |
since | string | Start time (RFC3339) |
until | string | End time (RFC3339) |
limit | int | Max results (default 50) |
offset | int | Pagination offset |
Response:
{ "runs": [], "count": 10, "total_count": 42}GET /runs/{id}
Section titled “GET /runs/{id}”Get a single run by ID.
GET /runs/{id}/steps
Section titled “GET /runs/{id}/steps”Get all steps executed in a run.
GET /runs/{id}/streams
Section titled “GET /runs/{id}/streams”List entity streams that were read from or appended to during this run.
GET /runs/{id}/audit
Section titled “GET /runs/{id}/audit”Get the history inspection (audit trail) for a specific run.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
event_type | string | Filter by audit event type |
from | string | Start timestamp |
to | string | End timestamp |
limit | int | Max results |
cursor | string | Pagination cursor |
POST /runs/{id}/cancel
Section titled “POST /runs/{id}/cancel”Cancel a pending, running, or paused run. Routes through the engine’s
cancel helper: marks the run terminal with cancellation_cause = "user",
deletes any cancel_on_specs rows referencing the run, publishes a
cancel event to NATS with a stable msg-id (cancel-{runID}), and
notifies pubsub subscribers. ConnectRPC CancelRun produces identical
behavior (#716).
Request (optional):
{ "reason": "No longer needed"}The reason field is captured into the NATS payload and structured
log only — the persisted cancellation_cause column is always "user".
Long reasons are truncated server-side (rune-aware, 1024 rune cap).
Responses:
200 OK— run cancelled (idempotent: re-cancelling an already-cancelled run also returns 200 with the cancelled run).404 Not Found— run id does not exist.409 Conflict— run reached a different terminal state during the cancel (race).412 Precondition Failed— run is in a non-cancellable status (already completed/failed/cancelled).500 Internal Server Error— unexpected store/engine failure.
POST /runs/resume
Section titled “POST /runs/resume”Resume a paused or failed run, optionally from a specific step. Routes
through the engine’s resume helper: flips status to running, publishes
a resume event to NATS with a stable msg-id (resume-{runID}), and
notifies pubsub subscribers. If the NATS publish fails the run status
is reverted to its prior paused/failed value so the run is not stuck
in running with no executor (#716).
Request:
{ "run_id": "run_abc", "from_step": "step_xyz"}from_step is optional; omit to resume from the last completed step.
Responses:
200 OK— run resumed; status flipped torunningand resume event published.400 Bad Request— malformed body or missingrun_id.404 Not Found— run id does not exist, ORfrom_stepdoes not exist, ORfrom_stepbelongs to a different run.412 Precondition Failed— run is in a non-resumable status (must bepausedorfailed).500 Internal Server Error— NATS publish failed (run is reverted to its prior status), or other unexpected failure.
POST /steps/patch
Section titled “POST /steps/patch”Manually override a step’s output (e.g., to fix a failed step before resuming).
Request:
{ "step_id": "step_abc", "output": { "result": "manual_fix" }, "reason": "Corrected API response"}History Editing (Scoped Injection)
Section titled “History Editing (Scoped Injection)”Pause Run
Section titled “Pause Run”Pause a running workflow at the next step boundary for inspection and injection.
POST /ironflow.v1.IronflowService/PauseRunRequest:
{ "run_id": "run_abc123"}Response:
{ "status": "pause_requested"}Status is "pause_requested" if the run is running (will pause at next step boundary), or "paused" if the run was already paused (sleep/wait) and was upgraded to injection pause.
Get Paused State
Section titled “Get Paused State”Get completed steps and their outputs for an injection-paused run.
POST /ironflow.v1.IronflowService/GetPausedStateRequest:
{ "run_id": "run_abc123"}Response:
{ "steps": [ { "id": "step_xyz", "name": "compute-total", "output": "{\"total\": 42}", "injected": false, "completed_at": "2026-03-08T00:00:00Z" } ], "next_step_hint": "send-email", "pause_reason": "injection"}Only works on runs with status = "paused" and pause_reason = "injection".
Inject Step Output
Section titled “Inject Step Output”Modify the output of a completed step while a run is paused for injection.
POST /ironflow.v1.IronflowService/InjectStepOutputRequest:
{ "run_id": "run_abc123", "step_id": "step_xyz", "new_output": "{\"total\": 50}", "reason": "Correcting calculation error"}Response:
{ "step_id": "step_xyz", "previous_output": "{\"total\": 42}"}Preserves the original output on first injection. Sets patched_at and patched_by on the step.
Environments
Section titled “Environments”GET /environments
Section titled “GET /environments”List all environments. API keys are masked in the response.
POST /environments
Section titled “POST /environments”Create a new environment.
Request:
{ "name": "staging"}PUT /environments/{id}
Section titled “PUT /environments/{id}”Update an environment (e.g., rename it).
Request:
{ "name": "production"}DELETE /environments/{id}
Section titled “DELETE /environments/{id}”Delete an environment. The default environment cannot be deleted.
Projects
Section titled “Projects”GET /projects
Section titled “GET /projects”List all projects in the organization.
POST /projects
Section titled “POST /projects”Create a new project.
Request:
{ "name": "payments"}PUT /projects/{id}
Section titled “PUT /projects/{id}”Update a project.
DELETE /projects/{id}
Section titled “DELETE /projects/{id}”Delete a project.
Entity Streams (Event Sourcing)
Section titled “Entity Streams (Event Sourcing)”GET /streams
Section titled “GET /streams”List all entity streams.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
entity_type | string | Filter by entity type |
search | string | Search streams |
limit | int | Max results |
offset | int | Pagination offset |
GET /streams/{entityId}
Section titled “GET /streams/{entityId}”Get stream metadata (current version, entity type).
GET /streams/{entityId}/events
Section titled “GET /streams/{entityId}/events”Read events from an entity stream.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
from_version | int | Start from version |
limit | int | Max events to return |
direction | string | Read direction |
POST /streams/{entityId}/events
Section titled “POST /streams/{entityId}/events”Append an event to an entity stream with optimistic concurrency.
Request:
{ "entity_type": "order", "event_name": "order.shipped", "data": { "tracking": "1Z999" }, "expected_version": 3, "idempotency_key": "ship-order-123", "version": 1}Response:
{ "event_id": "evt_abc", "entity_version": 4}GET /streams/{entityId}/history
Section titled “GET /streams/{entityId}/history”Get entity state history (reconstructed state at each version).
POST /streams/{entityId}/snapshots
Section titled “POST /streams/{entityId}/snapshots”Create a snapshot of the entity stream’s current state.
GET /streams/{entityId}/snapshots
Section titled “GET /streams/{entityId}/snapshots”Get the latest snapshot for an entity stream.
Projections
Section titled “Projections”GET /projections
Section titled “GET /projections”List all registered projections.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by status |
mode | string | Filter by mode |
limit | int | Max results |
offset | int | Pagination offset |
GET /projections/{name}
Section titled “GET /projections/{name}”Get a projection and its current state.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
partition | string | Partition key (default: __global__) |
GET /projections/{name}/status
Section titled “GET /projections/{name}/status”Get projection processing status.
Response:
{ "name": "order-totals", "status": "running", "mode": "managed", "last_event_seq": 142, "error_message": null, "updated_at": "2026-03-06T12:00:00Z"}GET /projections/{name}/partitions
Section titled “GET /projections/{name}/partitions”List partitions for a partitioned projection.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
limit | int | Max results |
offset | int | Pagination offset |
GET /projections/{name}/catchup
Section titled “GET /projections/{name}/catchup”Wait until a projection has caught up to a minimum NATS sequence. Long-poll style; returns when the projection is current or the request times out.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
partition | string | Partition key (default: __global__) |
minSeq | int | Minimum NATS sequence to wait for |
timeoutMs | int | Max wait in milliseconds (default: 30000) |
POST /projections/catchup/batch
Section titled “POST /projections/catchup/batch”Wait for multiple projections to catch up in one call.
Request:
{ "items": [ { "name": "order-totals", "minSeq": 142, "partition": "__global__" }, { "name": "user-index", "minSeq": 142 } ], "timeoutMs": 5000}POST /projections/wait-for-event
Section titled “POST /projections/wait-for-event”Block until a specific event has been applied by a projection. Useful after AppendEvent when AppendEventResponse.Sequence is 0.
Request:
{ "eventId": "evt_abc", "projection": "order-totals", "partition": "__global__", "timeoutMs": 5000}POST /projections/{name}/rebuild
Section titled “POST /projections/{name}/rebuild”Trigger a full or partial projection rebuild.
Request (optional):
{ "fromEventId": "evt_start", "toEventId": "evt_end", "partition": "tenant-1", "dryRun": false}GET /projections/{name}/rebuild
Section titled “GET /projections/{name}/rebuild”Get the status of an ongoing rebuild job.
POST /projections/{name}/cancel
Section titled “POST /projections/{name}/cancel”Cancel an ongoing rebuild.
POST /projections/{name}/pause
Section titled “POST /projections/{name}/pause”Pause a projection (stops consuming events).
POST /projections/{name}/resume
Section titled “POST /projections/{name}/resume”Resume a paused projection.
DELETE /projections/{name}
Section titled “DELETE /projections/{name}”Delete/unregister a projection.
Pub/Sub
Section titled “Pub/Sub”POST /publish
Section titled “POST /publish”Publish a message to a developer pub/sub topic.
Request:
{ "topic": "notifications.email", "data": { "to": "user@example.com", "subject": "Hello" }, "idempotency_key": "notif-123"}Response:
{ "event_id": "evt_abc", "sequence": 42}Topics with reserved prefixes (events:, system., entity:, public., topic:) are rejected.
Event Schema Registry
Section titled “Event Schema Registry”POST /events/schemas
Section titled “POST /events/schemas”Register a new event schema version.
Request:
{ "event_name": "order.placed", "version": 2, "schema_json": "{\"type\":\"object\",\"properties\":{...}}", "description": "Added shipping address field"}GET /events/schemas
Section titled “GET /events/schemas”List all event schemas.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
event_name | string | Filter by event name |
limit | int | Max results |
offset | int | Pagination offset |
GET /events/schemas/{name}
Section titled “GET /events/schemas/{name}”Get the latest version of a schema.
GET /events/schemas/{name}/{version}
Section titled “GET /events/schemas/{name}/{version}”Get a specific version of a schema.
DELETE /events/schemas/{name}/{version}
Section titled “DELETE /events/schemas/{name}/{version}”Delete a schema version.
POST /events/upcast
Section titled “POST /events/upcast”Test event upcasting between schema versions.
Request:
{ "event_name": "order.placed", "from_version": 1, "to_version": 2, "data": { "orderId": "123" }}Response:
{ "data": { "orderId": "123", "shipping_address": null }, "steps_applied": [...]}SQL Query
Section titled “SQL Query”POST /sql
Section titled “POST /sql”Execute an ad-hoc SQL query against the projection database.
Request:
{ "query": "SELECT * FROM order_totals WHERE total > 100", "timeout_ms": 5000, "max_rows": 100}Response:
{ "columns": ["id", "total"], "rows": [["order-1", 150]], "total_rows": 1}KV Store
Section titled “KV Store”POST /kv/buckets
Section titled “POST /kv/buckets”Create a KV bucket.
Request:
{ "name": "session-cache", "description": "User session data", "ttl_seconds": 3600, "max_value_size": 65536, "max_bytes": 10485760, "history": 5}GET /kv/buckets
Section titled “GET /kv/buckets”List all KV buckets.
GET /kv/buckets/{bucket}
Section titled “GET /kv/buckets/{bucket}”Get bucket metadata.
DELETE /kv/buckets/{bucket}
Section titled “DELETE /kv/buckets/{bucket}”Delete a bucket.
GET /kv/buckets/{bucket}/keys
Section titled “GET /kv/buckets/{bucket}/keys”List keys in a bucket.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
filter | string | Glob pattern to filter keys |
GET /kv/buckets/{bucket}/keys/{key}
Section titled “GET /kv/buckets/{bucket}/keys/{key}”Retrieve a key’s value. Returns an ETag header with the revision.
PUT /kv/buckets/{bucket}/keys/{key}
Section titled “PUT /kv/buckets/{bucket}/keys/{key}”Write a value. Supports atomic operations via headers:
| Header | Effect |
|---|---|
If-None-Match: * | Create only (fails if key exists) |
If-Match: <revision> | Compare-and-swap (fails if revision differs) |
Request body is the raw value (any content type).
DELETE /kv/buckets/{bucket}/keys/{key}
Section titled “DELETE /kv/buckets/{bucket}/keys/{key}”Delete a key.
GET /kv/buckets/{bucket}/watch
Section titled “GET /kv/buckets/{bucket}/watch”Watch a bucket for key changes via WebSocket upgrade.
Configuration
Section titled “Configuration”POST /config/{name}
Section titled “POST /config/{name}”Set or replace an entire config document.
Request: Any JSON object.
Response:
{ "name": "app-settings", "revision": 1}PATCH /config/{name}
Section titled “PATCH /config/{name}”Shallow merge-patch a config document with CAS retry.
Request: Partial JSON object with fields to merge.
GET /config/{name}
Section titled “GET /config/{name}”Get a config document.
Response:
{ "name": "app-settings", "data": { "theme": "dark" }, "revision": 3, "updatedAt": "2026-03-06T12:00:00Z"}GET /config
Section titled “GET /config”List all config documents.
DELETE /config/{name}
Section titled “DELETE /config/{name}”Delete a config document.
GET /config/{name}/watch
Section titled “GET /config/{name}/watch”Watch a config document for real-time changes via WebSocket.
Secrets
Section titled “Secrets”All secrets endpoints require the X-Ironflow-Environment header. Secret values are write-only and never returned in responses.
GET /secrets
Section titled “GET /secrets”List secret metadata.
POST /secrets
Section titled “POST /secrets”Create a new secret.
Request:
{ "name": "STRIPE_KEY", "value": "sk_live_...", "description": "Production billing key"}GET /secrets/{name}
Section titled “GET /secrets/{name}”Get secret metadata (not the value).
PUT /secrets/{name}
Section titled “PUT /secrets/{name}”Update a secret’s value.
Request:
{ "value": "sk_live_new_...", "description": "Rotated key"}PATCH /secrets/{name}
Section titled “PATCH /secrets/{name}”Partial update (description only).
DELETE /secrets/{name}
Section titled “DELETE /secrets/{name}”Delete a secret.
Webhooks
Section titled “Webhooks”POST /webhooks/{provider}
Section titled “POST /webhooks/{provider}”Receive a webhook from an external provider (e.g., GitHub, Stripe).
Supports signature verification (HMAC-SHA256/SHA1) and idempotency by external delivery ID.
Response:
{ "status": "accepted", "delivery_id": "gh_123", "event_id": "evt_abc", "run_ids": ["run_xyz"]}Workers (Pull Mode)
Section titled “Workers (Pull Mode)”GET /workers
Section titled “GET /workers”List all connected REST-polling workers.
POST /workers/{workerId}/register
Section titled “POST /workers/{workerId}/register”Register a worker for HTTP polling.
Request:
{ "worker_id": "worker-1", "hostname": "host.example.com", "function_ids": ["my-function"], "max_concurrent_jobs": 5, "labels": { "region": "us-east" }, "version": { "sdk": "0.8.0", "runtime": "node-22" }}POST /workers/{workerId}/heartbeat
Section titled “POST /workers/{workerId}/heartbeat”Send a heartbeat to keep the worker connection alive.
Request:
{ "worker_id": "worker-1", "active_jobs": 2}GET /workers/{workerId}/jobs
Section titled “GET /workers/{workerId}/jobs”Poll for job assignments. Returns 204 No Content if no jobs are available.
Response (when job available):
{ "job_id": "job_abc", "run_id": "run_xyz", "function_id": "my-function", "attempt": 1, "event": { ... }, "completed_steps": [ ... ], "secrets": { ... }}PUT /workers/{workerId}/jobs/{jobId}
Section titled “PUT /workers/{workerId}/jobs/{jobId}”Report job completion, failure, or yield.
Request:
{ "status": "completed", "output": { "result": "done" }, "steps": [ ... ]}Status values: completed, failed, yielded.
API Keys
Section titled “API Keys”POST /apikeys
Section titled “POST /apikeys”Create a new API key. Set "platform": true to create a platform key (requires platform:keys:manage).
Request:
{ "name": "CI Pipeline", "env_id": "env_production", "role_ids": ["role_admin"], "expires_in": "720h"}Response includes the raw key (only shown once).
GET /apikeys
Section titled “GET /apikeys”List API keys for the caller’s organization. Pass ?platform=true to list platform API keys instead (requires platform:keys:read).
GET /apikeys/{id}
Section titled “GET /apikeys/{id}”Get a single API key by ID.
DELETE /apikeys/{id}
Section titled “DELETE /apikeys/{id}”Delete/revoke an API key. Returns 204.
POST /apikeys/{id}/rotate
Section titled “POST /apikeys/{id}/rotate”Rotate an API key (creates new key, revokes old). Returns the new raw key.
PUT /apikeys/{id}/roles
Section titled “PUT /apikeys/{id}/roles”Replace the full set of role assignments on an API key.
Request:
{ "role_ids": ["role_admin", "role_custom_abc"]}Response: The updated API key object (without the raw key).
POST /users
Section titled “POST /users”Create a new dashboard user (admin only).
Request:
{ "email": "user@example.com", "password": "securepassword", "name": "Jane Doe", "roles": ["admin"]}GET /users
Section titled “GET /users”List all users in the caller’s organization (admin only).
GET /users/{id}
Section titled “GET /users/{id}”Get a single user (admin or self).
PATCH /users/{id}
Section titled “PATCH /users/{id}”Update user profile (admin only).
Request:
{ "name": "Jane Smith", "email": "jane@example.com", "roles": ["admin", "viewer"]}DELETE /users/{id}
Section titled “DELETE /users/{id}”Delete a user (admin only, cannot delete self). Returns 204.
PATCH /users/{id}/password
Section titled “PATCH /users/{id}/password”Change own password.
Request:
{ "current_password": "old_password", "new_password": "new_password"}POST /auth/login
Section titled “POST /auth/login”Authenticate and receive a JWT token for the dashboard. Public endpoint.
Request:
{ "email": "admin@example.com", "password": "password"}Response:
{ "token": "eyJhbGci..."}GET /auth/validate
Section titled “GET /auth/validate”Validate the current JWT token. Public endpoint — used by the dashboard to detect stale sessions on load.
Response (valid token):
{ "valid": true, "email": "admin@example.com"}Response (invalid/expired): 401 Unauthorized
Circuit Breakers
Section titled “Circuit Breakers”GET /circuit-breakers
Section titled “GET /circuit-breakers”List all circuit breaker states.
Response:
{ "circuit_breakers": [ { "key": "fn_abc|https://example.com/api/handler", "state": "open", "failures": 5, "last_failure": "2024-01-15T10:30:00Z" } ]}POST /circuit-breakers/{key}/reset
Section titled “POST /circuit-breakers/{key}/reset”Reset a circuit breaker to closed state. The key format is {functionID}|{endpointURL} — URL-encode the | as %7C in the path.
Response: 200 OK
Debounce
Section titled “Debounce”Operator endpoints for pending debounce entries (issue #545). Debounce collapses rapid-fire events for a function into a single invocation after a quiet period. See the debounce how-to for background. The CLI ironflow debounce {list|cancel} wraps these endpoints.
GET /debounce/entries
Section titled “GET /debounce/entries”List currently-armed debounce entries scoped to the request environment. Empty 200 when the engine is not wired for debounce (e.g., embedded/test mode without JetStream).
Response:
[ { "environment_id": "env_default", "function_id": "process-search", "debounce_key": "user-42", "event_id": "evt_01HW...", "function_version": 1, "period_ms": 5000, "armed_at": "2026-04-24T12:00:00.000Z", "fires_at": "2026-04-24T12:00:05.000Z" }]ArmedAt / FiresAt are ISO-8601 UTC millisecond timestamps. DebounceKey is the resolved key value (never base64-encoded in the response body — only the path parameter on cancel is encoded).
DELETE /debounce/entries/{envID}/{fnID}/{key}
Section titled “DELETE /debounce/entries/{envID}/{fnID}/{key}”Cancel one pending debounce entry without firing it. The key path segment MUST be base64url-encoded (base64.RawURLEncoding) because debounce keys can contain / or . which conflict with URL path semantics.
Path parameters:
| Parameter | Description |
|---|---|
envID | Environment ID owning the entry |
fnID | Function ID |
key | base64url-encoded debounce key |
Responses:
| Status | Meaning |
|---|---|
204 No Content | Entry cancelled, or did not exist (idempotent no-op) |
400 Bad Request | Path segments missing or key not valid base64url |
403 Forbidden | Tenant-scoped request whose authenticated env does not match envID in the path (cross-tenant guard) |
503 Service Unavailable | Debounce manager not configured on this server |
Tenant-scoped credentials can only cancel entries within their own environment. Platform-scoped credentials (empty RequestContext.EnvironmentID) may cancel any environment’s entry.
Outbox Dead-Letter
Section titled “Outbox Dead-Letter”Operator endpoints for the transactional outbox dead-letter table (issue #487, DLQ tooling from #496). Rows appear here after the outbox worker exhausts its retry budget (default 10 attempts). The underlying row in events is untouched — these endpoints only affect the unpublished NATS delivery.
See the outbox explanation and the DLQ runbook for context. The CLI ironflow outbox dlq {list|requeue|discard} wraps these endpoints.
GET /outbox/dead-letter
Section titled “GET /outbox/dead-letter”List rows in the outbox dead-letter table for one environment, newest first. The environment must be declared — callers send either the X-Ironflow-Environment header or the env query parameter (the CLI sends both). Missing both returns 400.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
env | string | — | Environment name (required unless X-Ironflow-Environment header is set) |
limit | int | 50 | Max rows to return (1-500, clamped) |
offset | int | 0 | Row offset for pagination (0-10000) |
Response:
{ "items": [ { "id": "ob_01HW...", "event_id": "evt_abc123", "entity_id": "order_42", "topic": "orders.placed", "kind": "events", "environment_id": "env_prod", "created_at": "2026-04-20T12:00:00Z", "dead_at": "2026-04-20T12:05:30Z", "attempts": 10, "last_error": "nats: max payload exceeded", "payload": "<base64>", "metadata": "<base64>" } ], "limit": 50, "offset": 0}payload and metadata are base64-encoded to keep the JSON body valid for binary content.
POST /outbox/dead-letter/{eventID}/requeue
Section titled “POST /outbox/dead-letter/{eventID}/requeue”Move every dead-letter row matching event_id back to the live outbox with attempts=0. The worker picks them up on the next tick.
Response: 200 OK
{ "event_id": "evt_abc123", "result": "requeued" }404 Not Found if no dead-letter entry exists for the given event_id.
DELETE /outbox/dead-letter/{eventID}
Section titled “DELETE /outbox/dead-letter/{eventID}”Permanently delete a dead-letter row. The underlying events row is not deleted — only the unpublished outbox entry. Destructive; the CLI prompts for confirmation before calling this.
Response: 200 OK
{ "event_id": "evt_abc123", "result": "discarded" }404 Not Found if no dead-letter entry exists for the given event_id.
History Inspection (Audit Trail)
Section titled “History Inspection (Audit Trail)”GET /audit
Section titled “GET /audit”List history inspection (audit) events globally.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
run_id | string | Filter by run |
function_id | string | Filter by function |
event_type | string | Filter by audit event type |
from | string | Start timestamp |
to | string | End timestamp |
limit | int | Max results |
cursor | string | Pagination cursor |
System
Section titled “System”GET /health
Section titled “GET /health”Liveness probe. Returns 200 when the database Ping succeeds, 503 otherwise. Does not check NATS — use /ready for full readiness. Public endpoint (no auth required).
GET /capabilities
Section titled “GET /capabilities”Returns server edition, supported features, and transport information. Public endpoint.
Response:
{ "version": "0.8.0", "edition": "core", "auth_required": true, "transports": ["websocket", "grpc-sse", "grpc-bidirectional"], "features": ["replay", "wildcard-patterns", "cel-filter", "consumer-groups", "prometheus-metrics"]}GET /ready
Section titled “GET /ready”Readiness probe endpoint. Returns 200 when the server is ready to accept traffic (PostgreSQL and NATS connected). Used by Kubernetes readiness probes.
GET /routes
Section titled “GET /routes”Returns the server route manifest. Used by SDK generation tooling (make sdk-manifest).
GET /overview
Section titled “GET /overview”Get a comprehensive system overview with stats and uptime.
GET /cluster/health
Section titled “GET /cluster/health”Returns cluster health status including node information. Available in multi-node cluster mode.
GET /ws
Section titled “GET /ws”Open a WebSocket connection for real-time event subscriptions.
GET /metrics
Section titled “GET /metrics”Prometheus metrics endpoint (only available when metrics are enabled).
Debugging
Section titled “Debugging”GET /debug/requests
Section titled “GET /debug/requests”Get recently captured HTTP requests (up to 100).
DELETE /debug/requests
Section titled “DELETE /debug/requests”Clear all captured debug requests.
Organization & Policy Endpoints
Section titled “Organization & Policy Endpoints”Organizations
Section titled “Organizations”| Method | Path | Description |
|---|---|---|
POST | /orgs | Create an organization |
GET | /orgs | List organizations |
GET | /orgs/{id} | Get an organization |
PATCH | /orgs/{id} | Update organization name |
DELETE | /orgs/{id} | Delete an organization |
| Method | Path | Description |
|---|---|---|
POST | /roles | Create a custom role |
GET | /roles | List roles |
GET | /roles/{id} | Get a role |
PATCH | /roles/{id} | Update role name |
DELETE | /roles/{id} | Delete a role (not built-in) |
POST | /roles/{id}/policies | Assign a policy to a role |
DELETE | /roles/{id}/policies/{policy_id} | Remove a policy from a role |
Policies
Section titled “Policies”| Method | Path | Description |
|---|---|---|
POST | /policies | Create a CEL authorization policy |
GET | /policies | List policies |
GET | /policies/{id} | Get a policy |
PATCH | /policies/{id} | Update a policy |
DELETE | /policies/{id} | Delete a policy |
POST | /policies/dry-run | Evaluate a candidate policy against sample subjects without persisting it (self-lockout preflight) |
GET | /policies/{id}/versions | List version history for a policy |
POST | /policies/{id}/rollback/{version} | Forward-rollback a policy to a prior version (writes a new version+1 snapshot) |
Policy Templates
Section titled “Policy Templates”| Method | Path | Description |
|---|---|---|
GET | /policy-templates | List installable CEL policy template bundles |
POST | /policy-templates/{id}/install | Install a template bundle into the caller’s tenant (runs LintTemplate first) |
Policy Request:
{ "name": "allow-read-events", "effect": "allow", "actions": "read", "resources": "events/*", "condition": "request.environment == 'production'"}Tenants
Section titled “Tenants”| Method | Path | Description |
|---|---|---|
POST | /tenants/provision | Provision a tenant (org + env + admin key) |
GET | /tenants | List tenants with environment and key counts |
Provision Request:
{ "org_name": "Acme Corp", "env_name": "production"}Provision Response:
{ "org": { "id": "org_abc", "name": "Acme Corp" }, "environment": { "id": "env_production", "name": "production" }, "api_key": { "key": "ifkey_...", "roles": ["admin"] }}Common Response Codes
Section titled “Common Response Codes”| Code | Meaning |
|---|---|
200 | Success |
201 | Resource created |
204 | Success, no content |
400 | Invalid request or validation error |
401 | Authentication required |
403 | Insufficient permissions |
404 | Resource not found |
409 | Conflict (duplicate resource or version mismatch) |
412 | Precondition failed (CAS mismatch) |
503 | Service unavailable |