Skip to content

REST API

Ironflow exposes a REST API for managing functions, emitting events, querying runs, and more.

Base URL: http://localhost:9123/api/v1


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_here

Dashboard JWT (browser sessions): Dashboard authentication uses the ironflow_dashboard_token cookie set by the login endpoint.

Environment Selection (optional):

X-Ironflow-Environment: env_production

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

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_cursor are null/omitted when there is no further page in that direction.
  • has_next and has_prev are direction-aware. On the newest page (no cursor and no before), has_prev is always false. After paging forward with cursor=, has_prev is true. After paging backward with before=, has_next is true.
  • approx_total is the per-environment count from a capped subquery (LIMIT 10001). Below the cap the value is exact; above the cap the value is 10000 and approx_total_capped is true.

Errors:

  • 400 — invalid cursor / before token, or both supplied at once.

Get a single event by ID.


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

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

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

POST /ironflow.v1.IronflowService/ListFunctionHistory

Request:

{
"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.

POST /ironflow.v1.IronflowService/GetFunctionAtVersion

Request:

{
"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"
}
}
POST /ironflow.v1.IronflowService/RollbackFunction

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


List workflow runs with optional filters.

Query Parameters:

Parameter Type Description
function_id string Filter by function
status string Filter by status (comma-separated: waiting_for_capacity, waiting, 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 a single run by ID.

Get all steps executed in a run.

List entity streams that were read from or appended to during this run.

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

Cancel a queued (waiting_for_capacity / waiting), 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.

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 to running and resume event published.
  • 400 Bad Request — malformed body or missing run_id.
  • 404 Not Found — run id does not exist, OR from_step does not exist, OR from_step belongs to a different run.
  • 412 Precondition Failed — run is in a non-resumable status (must be paused or failed).
  • 500 Internal Server Error — NATS publish failed (run is reverted to its prior status), or other unexpected failure.

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

Pause a running workflow at the next step boundary for inspection and injection.

POST /ironflow.v1.IronflowService/PauseRun

Request:

{
"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 completed steps and their outputs for an injection-paused run.

POST /ironflow.v1.IronflowService/GetPausedState

Request:

{
"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".

Modify the output of a completed step while a run is paused for injection.

POST /ironflow.v1.IronflowService/InjectStepOutput

Request:

{
"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.


List all environments. API keys are masked in the response.

Create a new environment.

Request:

{
"name": "staging"
}

Update an environment (e.g., rename it).

Request:

{
"name": "production"
}

Delete an environment. The default environment cannot be deleted.


List all projects in the organization.

Create a new project.

Request:

{
"name": "payments"
}

Update a project.

Delete a project.


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 stream metadata (current version, entity type).

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

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 entity state history (reconstructed state at each version).

Create a snapshot of the entity stream’s current state.

Get the latest snapshot for an entity stream.


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 a projection and its current state.

Query Parameters:

Parameter Type Description
partition string Partition key (default: __global__)

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

List partitions for a partitioned projection.

Query Parameters:

Parameter Type Description
limit int Max results
offset int Pagination offset

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)

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
}

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
}

Trigger a full or partial projection rebuild.

Request (optional):

{
"fromEventId": "evt_start",
"toEventId": "evt_end",
"partition": "tenant-1",
"dryRun": false
}

Get the status of an ongoing rebuild job.

Cancel an ongoing rebuild.

Pause a projection (stops consuming events).

Resume a paused projection.

Delete/unregister a projection.


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.


Register a new event schema version.

Request:

{
"event_name": "order.placed",
"version": 2,
"schema_json": "{\"type\":\"object\",\"properties\":{...}}",
"description": "Added shipping address field"
}

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 the latest version of a schema.

Get a specific version of a schema.

Delete a schema version.

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": [...]
}

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
}

Create a KV bucket.

Request:

{
"name": "session-cache",
"description": "User session data",
"ttl_seconds": 3600,
"max_value_size": 65536,
"max_bytes": 10485760,
"history": 5
}

List all KV buckets.

Get bucket metadata.

Delete a bucket.

List keys in a bucket.

Query Parameters:

Parameter Type Description
filter string Glob pattern to filter keys

Retrieve a key’s value. Returns an ETag header with the revision.

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 a key.

Watch a bucket for key changes via WebSocket upgrade.


Set or replace an entire config document.

Request: Any JSON object.

Response:

{
"name": "app-settings",
"revision": 1
}

Shallow merge-patch a config document with CAS retry.

Request: Partial JSON object with fields to merge.

Get a config document.

Response:

{
"name": "app-settings",
"data": { "theme": "dark" },
"revision": 3,
"updatedAt": "2026-03-06T12:00:00Z"
}

List all config documents.

Delete a config document.

Watch a config document for real-time changes via WebSocket.


All secrets endpoints require the X-Ironflow-Environment header. Secret values are write-only and never returned in responses.

List secret metadata.

Create a new secret.

Request:

{
"name": "STRIPE_KEY",
"value": "sk_live_...",
"description": "Production billing key"
}

Get secret metadata (not the value).

Update a secret’s value.

Request:

{
"value": "sk_live_new_...",
"description": "Rotated key"
}

Partial update (description only).

Delete a secret.


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

List all connected REST-polling workers.

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

Send a heartbeat to keep the worker connection alive.

Request:

{
"worker_id": "worker-1",
"active_jobs": 2
}

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": { ... }
}

Report job completion, failure, or yield.

Request:

{
"status": "completed",
"output": { "result": "done" },
"steps": [ ... ]
}

Status values: completed, failed, yielded.


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

List API keys for the caller’s organization. Pass ?platform=true to list platform API keys instead (requires platform:keys:read).

Get a single API key by ID.

Delete/revoke an API key. Returns 204.

Rotate an API key (creates new key, revokes old). Returns the new raw key.

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


Create a new dashboard user (admin only).

Request:

{
"email": "user@example.com",
"password": "securepassword",
"name": "Jane Doe",
"roles": ["admin"]
}

List all users in the caller’s organization (admin only).

Get a single user (admin or self).

Update user profile (admin only).

Request:

{
"name": "Jane Smith",
"email": "jane@example.com",
"roles": ["admin", "viewer"]
}

Delete a user (admin only, cannot delete self). Returns 204.

Change own password.

Request:

{
"current_password": "old_password",
"new_password": "new_password"
}

Authenticate and receive a JWT token for the dashboard. Public endpoint.

Request:

{
"email": "admin@example.com",
"password": "password"
}

Response:

{
"token": "eyJhbGci..."
}

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


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

Reset a circuit breaker to closed state. The key format is {functionID}|{endpointURL} — URL-encode the | as %7C in the path.

Response: 200 OK


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.

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.


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 for context. The CLI ironflow outbox dlq {list|requeue|discard} wraps these endpoints.

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.

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.


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

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

Returns server version, supported features, and transport information. Public endpoint.

Response:

{
"version": "0.8.0",
"auth_required": true,
"transports": ["websocket", "grpc-sse", "grpc-bidirectional"],
"features": ["replay", "wildcard-patterns", "cel-filter", "consumer-groups", "prometheus-metrics"]
}

Readiness probe endpoint. Returns 200 when the server is ready to accept traffic (PostgreSQL and NATS connected). Used by Kubernetes readiness probes.

Returns the server route manifest. Used by SDK generation tooling (make sdk-manifest).

Get a comprehensive system overview with stats and uptime.

Returns cluster health status including node information. Available in multi-node cluster mode.

Open a WebSocket connection for real-time event subscriptions.

Prometheus metrics endpoint (only available when metrics are enabled).


Get recently captured HTTP requests (up to 100). Credential-bearing request headers (Authorization, Cookie, X-Api-Key, Proxy-Authorization) are stored as [REDACTED] so the inspector buffer can never be used to harvest a caller’s credentials.

Clear all captured debug requests.


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
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)
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": "deny-prod-deletes",
"effect": "deny",
"actions": "events:emit",
"resources": "irn:ironflow:*:*:event:env_prod:*",
"condition": "request.environment == 'env_prod'"
}

effect is deny only (#943, ADR 0016 T2). Submitting effect: "allow" returns 400 policy_effect_allow_deprecated. CEL policies are subtractive over the RBAC layer — to grant capability, edit role assignment, not write an allow policy.

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

Read-only inspection of the unified capacity/dispatch subsystem (ADR 0037, #1206). Any platform principal (read-only) — requires a ifplatform_ API key or a platform JWT; the gate is “is-platform”, not a specific platform role, so platform_admin, operator, and viewer principals can all read these views. Tenant API keys return 403.

All endpoints accept the same optional query parameters:

Parameter Type Description
env string Filter by environment ID
function string Filter by function ID (ignored for leases, credits, and stats)
limit int Max rows to return (default 100, hard-capped at 1000). Values above 1000 are silently clamped to 1000; non-positive values use the default. Ignored by stats.

Lane identities are returned as opaque HMAC digests. Lease entries carry lease_token_hash only — raw lane keys and lease tokens are never stored or returned.

List concurrency_lanes rows. Each row carries the lane digest, environment, function, scope, limit, and the maintained active_reserved counter.

List dispatch_queue segments. Returns queue position, run ID, execution sequence, execution mode, function, lane digest, blocking reason, and the eligible_at timestamp (when the segment becomes runnable).

List active (unexpired) concurrency_leases rows. The function query parameter is silently ignored — concurrency_leases has no function_id column. The JSON response includes lease_token_hash (not the raw token); the function_id field is present but always empty ("") on this endpoint — the active-leases path does not JOIN runs, so the owning function is not resolved here.

List dispatch_buckets fairness-ledger rows. Each row is keyed by (environment_id, function_id, lane_id) and carries last_dispatched_at.

List worker_sessions rows for active pull workers. Supports env and function filters; function matches sessions advertising that function via worker_session_functions.

List worker_credits rows (claimed and free slots). The function query parameter is silently ignored — worker_credits has no function_id column.

Return a per-(environment, function) capacity snapshot: active leases, lane limit, queued segments, outstanding reservations, and oldest queue age in seconds. The env, function, and limit query parameters are all ignored — stats always returns the full cross-tenant snapshot (one row per active env/function; row count is bounded by that cardinality, not by limit).

Example response (stats):

[
{
"environment_id": "env_default",
"function_id": "my-fn",
"active_leases": 2,
"lane_limit": 5,
"queued": 7,
"reservations": 1,
"oldest_queue_age_seconds": 12.4
}
]

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