---
title: Http Api
description: Every NullRun HTTP endpoint: /api/v1/gate, /api/v1/track, /api/v1/capabilities, /api/v1/heartbeat, and the control-plane WebSocket protocol.
---
This page lists the endpoints a user — or their SDK — actually calls. Internal admin endpoints (pricing backfill, gateway operator APIs) are not exposed here; see the gateway repo for the full internal surface.
Base URL:
- Production:
https://api.nullrun.io/api/v1 - WebSocket control plane:
wss://api.nullrun.io/ws/control/{org_id}
The {org_id} for the WebSocket comes from the credential bundle
returned by POST /api/v1/auth/verify (organization_id field).
The SDK negotiates this automatically — only set the URL by hand
when you are building a custom WebSocket client.
The OpenAPI spec is generated from the router and is the source of truth.
Two schemes — they are not interchangeable.
All SDK-traffic endpoints (/track, /track/batch, /gate,
/execute, /check) require:
| Header | Value |
|---|---|
X-API-Key |
API key (nr_live_...) |
X-Signature |
hex(HMAC-SHA256(secret_key, timestamp:api_key:body_sha256)) |
X-Signature-Timestamp |
Unix epoch seconds (rejected if older than NULLRUN_HMAC_MAX_AGE_SECS, default 300) |
X-Workflow-Id (optional) |
binds the call to a workflow context |
The SDK computes and signs every request automatically once
NULLRUN_API_KEY and NULLRUN_SECRET_KEY are set. The gateway
default for NULLRUN_HMAC_REQUIRED is false for backward
compatibility — operators must set it explicitly to true in
production. When NULLRUN_HMAC_REQUIRED=true, unsigned SDK requests
are rejected with 401 and the SDK-auth middleware emits a per-request
WARN so the gap is visible in logs.
SDK requests to
/api/v1/orgs/{org_id}/*are also checked for org-mismatch: the org claimed in the URL must match the org the API key was minted for. Mismatch → 403.
Dashboard endpoints (/orgs/..., /admin/...) use session tokens
obtained from POST /api/v1/auth/login or the OAuth flow
(/auth/oauth/register). Pass as Authorization: Bearer <token>.
These are the endpoints the SDK calls. You will not normally hit them by hand, but the contracts are stable and the OpenAPI spec documents every field.
| Method | Path | Called by |
|---|---|---|
POST |
/api/v1/auth/verify |
init() — verifies the API key and returns the full credential bundle (HMAC secret_key, org context, plan, workflow_id, key_version, etc.) |
GET |
/api/v1/capabilities |
init() — protocol negotiation |
POST |
/api/v1/gate |
Pre-flight + policy evaluation + budget reservation (called from @protect entry) |
POST |
/api/v1/track |
Single-event commit (reservation_id + idempotency_key) |
POST |
/api/v1/track/batch |
Batched events (≤ 100 per batch) — opt-in fallback when single-event is disabled |
GET |
/api/v1/orgs/{org_id}/policies |
Policy fetch (called from SDK on first @protect and on policy_invalidated WS push) |
GET |
/api/v1/orgs/{org_id}/workflows/{workflow_id} |
Workflow lookup (called from SDK on first gate per workflow) |
GET |
/api/v1/orgs/{org_id}/status |
Control-plane poll fallback (only used when WS is down) |
POST |
/api/v1/heartbeat |
Time-based cadence heartbeat |
POST |
/api/v1/cancel |
Cancel an in-flight execution. Idempotent; the reservation TTL cleans up even if the call doesn't reach the gateway. |
| Method | Path | Status |
|---|---|---|
POST |
/api/v1/check |
Deprecated — returns 410 Gone with replacement: /api/v1/gate. Use /gate for new integrations. |
POST |
/api/v1/execute |
Live — per-tool invocation with gate pre-flight and budget reservation (thin adapter in front of the unified gate engine). |
| Method | Path | Purpose |
|---|---|---|
POST |
/api/v1/auth/register |
Create account, returns API key + secret |
POST |
/api/v1/auth/login |
Dashboard session token |
POST |
/api/v1/auth/verify |
Verify API key + return full credential bundle |
POST |
/api/v1/auth/oauth/register |
OAuth signup (returns API key + secret) |
| Field | Type | Notes |
|---|---|---|
organization_id |
string (UUID) | Canonical org identifier |
organization_name |
string | null | Canonical org display name |
plan |
string | Plan tier (lite, starter, growth, scale, enterprise_unlimited, …) |
features |
object | Plan-feature flags resolved for this org |
limits |
object | Plan-limits block (workflows, seats, …) |
role |
string | null | Member role; null on API-key path — SDK treats null as "role unknown, escalate to session auth" |
workflow_id |
string | null | Workflow this API key is bound to; null on unbound / legacy keys |
secret_key |
string | null | HMAC secret the SDK uses to sign requests — distinct from the API key itself |
key_version |
number | null | Current active HMAC key version; SDK compares against its cached value to detect rotation |
| Method | Path | Purpose |
|---|---|---|
POST |
/api/v1/orgs/{org_id}/workflows |
Create workflow |
GET |
/api/v1/orgs/{org_id}/workflows |
List workflows |
GET |
/api/v1/orgs/{org_id}/workflows/{workflow_id} |
Get workflow |
PATCH |
/api/v1/orgs/{org_id}/workflows/{workflow_id} |
Update (budget, name, …) |
POST |
/api/v1/orgs/{org_id}/workflows/{workflow_id}/pause |
Pause (broadcasts state_change over WS) |
POST |
/api/v1/orgs/{org_id}/workflows/{workflow_id}/resume |
Resume |
POST |
/api/v1/orgs/{org_id}/workflows/{workflow_id}/kill |
Kill (broadcasts state_change over WS) |
| Method | Path | Purpose |
|---|---|---|
POST |
/api/v1/orgs/{org_id}/policies |
Create |
GET |
/api/v1/orgs/{org_id}/policies |
List (a single policy is not addressable by id — read it from the list response) |
PATCH |
/api/v1/orgs/{org_id}/policies/{policy_id} |
Update |
DELETE |
/api/v1/orgs/{org_id}/policies/{policy_id} |
Delete |
POST |
/api/v1/orgs/{org_id}/policies/{policy_id}/toggle |
Toggle a policy active/inactive (dashboard PATCH 404 fix) |
GET |
/api/v1/orgs/{org_id}/policies/templates |
List policy templates |
POST |
/api/v1/orgs/{org_id}/policies/templates/{template_id}/enable |
Enable a template |
DELETE |
/api/v1/orgs/{org_id}/policies/templates/{template_id} |
Disable a template |
Most-restrictive-wins composition across applicable policies — see Concepts → Policies.
Programmatic approval / denial — useful for on-call bots and CI runbooks. Dashboard uses the same endpoints internally. See Concepts → Human approval for the end-to-end flow.
| Method | Path | Purpose |
|---|---|---|
POST |
/api/v1/orgs/{org_id}/approvals/{approval_id}/approve |
Approve a pending approval. Idempotent — already-approved returns 409 approval_already_decided. |
POST |
/api/v1/orgs/{org_id}/approvals/{approval_id}/deny |
Deny a pending approval. The SDK raises WorkflowKilledInterrupt for the parked agent. |
| Method | Path | Purpose |
|---|---|---|
GET |
/api/v1/orgs/{org_id}/executions |
List executions |
GET |
/api/v1/orgs/{org_id}/executions/{execution_id} |
One execution |
GET |
/api/v1/orgs/{org_id}/audit-log |
Audit trail |
GET |
/api/v1/orgs/{org_id}/audit-log/export |
Start an async audit-log export job (returns a job id) |
POST |
/api/v1/orgs/{org_id}/audit-log/export |
Same — POST variant, useful from web forms |
GET |
/api/v1/orgs/{org_id}/audit-log/export/{job_id}/status |
Poll export job status (pending / ready / failed) |
GET |
/api/v1/orgs/{org_id}/audit-log/export/{job_id}/download |
Download the exported file once status is ready |
GET |
/api/v1/orgs/{org_id}/traces |
List trace spans |
GET |
/api/v1/orgs/{org_id}/traces/{trace_id} |
One trace (full span tree) |
GET |
/api/v1/orgs/{org_id}/incidents |
Active and recent incidents (rate-limit outages, budget overruns, etc.) |
GET |
/api/v1/orgs/{org_id}/dashboard |
Dashboard payload |
GET |
/api/v1/orgs/{org_id}/control-center |
Single-call control-center view (workflows + recent decisions + alerts) |
GET |
/api/v1/orgs/{org_id}/usage |
Per-key usage breakdown (canonical) |
GET |
/api/v1/orgs/{org_id}/quota |
Per-key usage breakdown (legacy alias of /usage — same payload) |
GET |
/api/v1/budget/approximate |
Approximate budget view for UI display — see Budgets → Approximate budget endpoint |
GET |
/api/v1/orgs/{org_id}/status |
Single-call dashboard status (budget + rate + plan limits + time-to-exhaustion) |
POST /api/v1/cancel cancels an in-flight execution. Cancellation is
idempotent — calling it twice on the same execution is a no-op and
releases the reservation by TTL even if the call never reaches the
gateway. See Control plane for the
related kill / pause endpoints.
| Method | Path | Purpose |
|---|---|---|
GET/PATCH/DELETE |
/api/v1/orgs/{org_id} |
Org settings, update, delete |
GET |
/api/v1/orgs/{org_id}/api-keys |
List keys |
POST |
/api/v1/orgs/{org_id}/api-keys |
Mint key |
DELETE |
/api/v1/orgs/{org_id}/api-keys/{key_id} |
Revoke key |
POST |
/api/v1/orgs/{org_id}/api-keys/{key_id}/rotate |
Rotate the HMAC secret in place |
GET |
/api/v1/workflows/{workflow_id}/api-keys |
Per-workflow key listing |
GET |
/api/v1/orgs/{org_id}/members |
Members |
PATCH |
/api/v1/orgs/{org_id}/members/{member_id} |
Update member role |
DELETE |
/api/v1/orgs/{org_id}/members/{member_id} |
Remove member |
POST |
/api/v1/orgs/{org_id}/invites |
Invite |
DELETE |
/api/v1/orgs/{org_id}/invites/{invite_id} |
Revoke invite |
POST |
/api/v1/orgs/{org_id}/invites/{invite_id}/resend |
Resend invite email |
GET |
/api/v1/invites/{token} |
Public invite info |
POST |
/api/v1/invites/{token}/accept |
Public invite accept |
POST |
/api/v1/invites/{token}/decline |
Public invite decline |
| Method | Path | Purpose |
|---|---|---|
GET |
/api/v1/orgs/{org_id}/alerts |
Active alerts |
POST |
/api/v1/orgs/{org_id}/alerts/{alert_id}/dismiss |
Dismiss |
GET/POST |
/api/v1/orgs/{org_id}/alert-channels |
Channels |
GET/PATCH |
/api/v1/orgs/{org_id}/notification-settings |
Per-user settings |
Health endpoints are registered on the gateway's top-level router
— they are not under /api/v1. They return 200 OK when healthy,
503 otherwise, with a JSON body listing each dependency's status.
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
Alias for /health/live |
GET |
/healthz |
Alias for /health/live (Kubernetes convention) |
GET |
/health/live |
Liveness — process is up and accepting connections |
GET |
/health/ready |
Readiness — Postgres + Redis reachable |
GET |
/health/startup |
Startup — 200 after migrations complete, 503 while booting |
The capabilities endpoint reports the wire-contract version the
gateway supports. The SDK calls this on init() to negotiate
the protocol version and to surface a startup warning if the SDK
is older than what the gateway requires.
| Method | Path | Purpose |
|---|---|---|
GET |
/api/v1/capabilities |
Report min_protocol_version / max_protocol_version, sdk_min_version, server version + build timestamp, and the capabilities.* feature flags |
When init() detects that the SDK is older than the gateway's
required minimum version, it emits a warning so the operator sees
the gap before the first /gate call fails with 400 PROTOCOL_TOO_OLD.
The current wire-protocol version is 4 (min supported: 2).
init() negotiates the version automatically via /capabilities; you
do not need to set anything by hand.
Long-running workflows post a time-based cadence heartbeat so the
gateway can detect an orphaned workflow whose agent process has
crashed without sending a kill / pause signal. The recommended
cadence is advertised in capabilities.heartbeat_interval_seconds
(default 30s).
| Method | Path | Purpose |
|---|---|---|
POST |
/api/v1/heartbeat |
Time-based cadence heartbeat; body is { chain_id } only. workflow_id is derived from the API key, not passed in the body. |
The SDK posts heartbeats automatically inside the
NullRunRuntime background thread once init() has run; operators
do not need to call it manually.
| Path | Purpose |
|---|---|
WS /ws/control/{org_id} |
Real-time kill/pause/policy-invalidated/key-rotated events (HMAC-signed on connect) |
Server → client message types: initial_state, state_change,
policy_invalidated, key_rotated, resync_required, error,
pong, approval_resolved, subscribed.
Client → server message types: ack.
See Control plane for the full protocol and the SDK reaction matrix.
The examples below use the dashboard's Authorization: Bearer <session-token>
header (the token comes from POST /api/v1/auth/login). For SDK-traffic
endpoints substitute X-API-Key + HMAC headers — see the
Authentication section above.
TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
ORG_ID=8a3b1c7d-...!!! note "Compute the HMAC signature"
For SDK endpoints the X-Signature and X-Signature-Timestamp
headers are required. The signature is
HMAC-SHA256(secret_key, "<timestamp>:<api_key>:<sha256(body)>").
See Authentication → X-API-Key + HMAC.
curl -X POST "https://api.nullrun.io/api/v1/orgs/$ORG_ID/workflows" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "production-bot",
"description": "Customer-facing assistant",
"budget_cents": 5000,
"human_approvals_enabled": false
}'
# → 201 { "id": "wf_abc...", "name": "production-bot", ... }curl -X POST "https://api.nullrun.io/api/v1/orgs/$ORG_ID/workflows/wf_abc.../api-keys" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "prod-bot-key",
"scopes": ["gate", "track", "verify"]
}'
# → 201 {
# "id": "key_...",
# "key": "nr_live_xxxxxxxxxxxx", ← shown ONCE, store it
# "secret_key": "sk_...", ← shown ONCE, store it
# "workflow_id": "wf_abc...",
# "key_prefix": "nr_live_xxxxxx"
# }The raw key and secret_key are never returned again —
losing them means rotating the key. See
API keys → How to create a key.
# Kill — broadcasts state_change(killed) over WS to every connected SDK
curl -X POST "https://api.nullrun.io/api/v1/orgs/$ORG_ID/workflows/wf_abc.../kill" \
-H "Authorization: Bearer $TOKEN"
# Pause
curl -X POST "https://api.nullrun.io/api/v1/orgs/$ORG_ID/workflows/wf_abc.../pause" \
-H "Authorization: Bearer $TOKEN"
# Resume
curl -X POST "https://api.nullrun.io/api/v1/orgs/$ORG_ID/workflows/wf_abc.../resume" \
-H "Authorization: Bearer $TOKEN"curl "https://api.nullrun.io/api/v1/orgs/$ORG_ID/status" \
-H "Authorization: Bearer $TOKEN"
# → 200 {
# "current_spend_cents": 2340,
# "budget_cents": 5000,
# "time_to_exhaustion_secs": 86400,
# "rate_used": 12,
# "rate_limit_per_min": 60,
# "plan_caps": { ... }
# }Useful for dashboards and alerts — one call returns everything you need to show "you've used X of Y".