Documentation
Everything you need to instrument an agent, replay an incident, write a policy, and ship with confidence. Covers all editions — Community (free), Starter, Growth, Scale, Pro, and Enterprise.
Wrapping one agent takes three lines. Here's exactly how — SDK, OpenTelemetry, policies, self-hosting, and the full API.
How a team rolls this out →Overview
Runback is an observability and governance platform for AI agents. It captures every LLM call, tool call, and agent step — then lets you re-run any decision from the exact captured context, enforce policies, run regression tests, and export a tamper-evident audit record.

The platform has five layers:
Instrument with the Runback SDK or send traces via OpenTelemetry. Every span is stored in a Postgres database you own.
Re-run any step from the exact captured context — tools, retrieval, messages[] held fixed. Root-cause in minutes.
Write policies as JSON rules. They evaluate on every run in real time and can gate your CI pipeline.
Any run exports as a signed, re-executable audit record. Deterministic — no model calls required.
Approvals, incidents, trust chains, and regulatory mappings for teams operating in regulated environments.
Quick start (5 minutes)
CommunityThe fastest path: the managed hosted tier. You get a live dashboard in under 5 minutes without running any infrastructure.

npm install @runback/sdk ai
# "ai" is a peer dependency — the snippet in step 4 imports from it, so
# installing @runback/sdk alone fails with ERR_MODULE_NOT_FOUND.
# Not using the Vercel AI SDK? Install just @runback/sdk and see the tip below.
# Prefer zero-install? "Send a run with cURL" and "OpenTelemetry" below both
# work with nothing added to your project.@runback/sdk/core and use startRun() — same recording, no ai peer dependency to install. See Manual recording.export RUNBACK_API_KEY=rb_live_your_key_here
export RUNBACK_INGEST_URL=https://runback.dev # self-hosted? use your own originRUNBACK_INGEST_URL is required on the managed service. The SDK defaults to http://localhost:3000 so that it can never phone home from a self-hosted or air-gapped deployment — which means that without this line your events go to localhost and nothing is recorded.import { withDebugger } from "@runback/sdk";
import { generateText, stepCountIs } from "ai";
const dbg = withDebugger(model, {
runName: "support-agent",
apiKey: process.env.RUNBACK_API_KEY,
redact: "standard",
});
const result = await generateText({
model: dbg.model,
tools: dbg.tools(myTools),
stopWhen: stepCountIs(10),
prompt: userMessage,
});
// finish() resolves with whether the run actually reached Runback.
// Check it — an audit trail you did not confirm is not an audit trail.
const rec = await dbg.finish({ output: result.text, status: "success" });
if (!rec.ok) console.error("[runback] not recorded:", rec.error);dbg.finish() in the snippet above returns why.Prefer a one-liner? This installs, configures and sends a synthetic test run, so you can see the whole path working before touching your own agent:
curl -fsSL https://runback.dev/api/quickstart | RUNBACK_API_KEY=your_key bashYour first run
Once a run lands in your dashboard you can:
- Click into it — opens the time-travel debugger. Scrub the slider to any step.
- Switch to Inspect mode — see the raw request/response for any LLM or tool call.
- Download the audit record — a signed, re-executable bundle. Verifiable at runback.dev/verify.

Runs & spans
A run is one end-to-end execution of an agent — from the initial prompt to the final output. A span is a single step within that run: either an LLM call or a tool call.
| Object | What it is | Key fields |
|---|---|---|
| Run | One agent execution | run_id, name, status, input, output, started_at |
| LLM span | One model call | model, messages, response, tokens, latency_ms |
| Tool span | One tool call | tool_name, input, output, error, policy_block |
| Audit record | Signed bundle of all spans | content_digest, signature, re-executable |

Span IDs are stable — the same span replays identically every time, from the recording. No model call required.
Every figure in the app is a count over these runs, and opens into them. Click a number — failed runs on the Overview, a model's error rate on Model attribution, the error count on a compliance report — and you land on exactly the rows behind it, with a line stating what was filtered to. The run list takes those filters directly:
| Query | Shows |
|---|---|
| /app/runs?status=error | Runs that failed. Also success and running. |
| /app/runs?agent=billing-agent | One agent, by its recorded name. |
| /app/runs?model=gpt-4o | Runs attributed to a model — its first LLM call decides, the same rule cost attribution uses. |
| /app/runs?since=2026-09-01 | Runs created on or after an instant, which is what a period-scoped figure means. |
The whole loop — one incident, end to end
The sections below document each capability on its own. This one shows how they fit together, because that is the part a reference cannot tell you: what you actually do on the day an agent gets something wrong.
Everything here follows one incident — an agent issued a refund it should have escalated — from the moment it happened to the record you hand an auditor.
/app/runs?status=error and the failing run is there with its spans. Your first run
messages[] the model was given — not a summary, the context. Deterministic, offline, no model calls. Replay



npx @runback/verify, without an account and without trusting us. Signed audit records · The audit ledger
Nothing above requires you to have predicted the incident. That is the point of recording everything at decision grain: the evidence is already there when you need it, and the fix becomes a test rather than a memory.
Replay
Runback has two kinds of replay:
Rewind any run to any step. The full agent state — every prior tool output, every message in context — is reconstructed from the recording. Deterministic, offline, no model calls.
Community+Re-execute a specific LLM call with a different model or prompt. Runback freezes the recorded inputs and makes a fresh call to the model you choose. Use it to bisect which model change caused a regression.
CommunityRe-run your whole agent against a recorded run on a different model. Every recorded tool output and environment read is served back, so everything upstream stays fixed — and the replay goes live only where the new model actually diverges. Answers “what would this agent have done?”, not just “what did this one call return?”. See Deep replay.
Enterprise
Time-travel controls:
- Drag the slider to scrub to any step
- ← / → step backward and forward one span at a time
- Click any span in the transcript to jump directly to that step
- Switch to Inspect mode to see the raw request/response JSON for any step
Whole-run replay on another model goes further than one step. It re-runs the entire recorded run against a candidate, reusing tool outputs from the cassette by content, and continues past the first divergence instead of stopping there — so you see every decision that would change, not just the first.
The result reads as a diff. The leading run of identical decisions is stated once and stepped over; the fork opens itself; each diverging step names what actually changed — "now answers directly instead of calling issue_refund" — rather than leaving you to compare two blobs. A step whose tool has no recorded output is marked as needing a live call, because everything after it is what the candidate would do, not what it did.

Step replay is for:
- Verifying that switching to a cheaper model doesn't change behaviour on historical incidents
- Bisecting which model or prompt change caused a regression
- Building a counterfactual — “what would the agent have done if I'd used GPT-4o mini here?”
Deep replay — re-run the whole agent
EnterpriseTime-travel and step replay work on what was recorded. Deep replay answers a different question: what would this agent have done on a different model, given exactly the same world?
You cannot answer that by re-running the agent live — every downstream prompt would change, because the agent builds its later inputs from its earlier outputs. And you cannot answer it by replaying the recording, because a recording only ever replays itself. So replayRun() does both at once:
- Hit — the new model asks for something the recording already has. The recorded output is served, and your tool is not called. Everything upstream is held exactly fixed, with no live side effects.
- Miss — the new model asks for something different. That is a genuine divergence, so execution goes live from that point, and the boundary is recorded.
The result is not a pass/fail but a frontier: how far the run reproduced identically, and the exact step where behaviour forked.
import { replayRun } from "@runback/sdk";
const { value, outcome } = await replayRun({
runId: "run_abc123",
model: "claude-sonnet-4-6", // the candidate you are testing
apiKey: process.env.RUNBACK_API_KEY,
ingestUrl: "https://runback.dev",
// Your agent, with its tools wrapped so replay can intercept them.
// deps.model is the candidate; deps.tool wraps each tool you call.
agent: ({ tool, model }) =>
myAgent(model, {
search: tool("search", liveSearch),
refund: tool("refund", liveRefund),
}),
});
console.log(outcome.reproducedPrefix); // e.g. 7 — identical through step 7
console.log(outcome.frontier); // { kind: "llm", key: "...", name: "..." } or null
console.log(outcome.digestMatch); // true = byte-exact reproduction, zero misses
What the outcome tells you
| Field | Meaning |
|---|---|
| reproducedPrefix | Consecutive hits before the first miss — "identical through step N". |
| frontier | The first divergence, or null if the run never diverged. |
| hits / misses | How much was served from the recording versus run live. |
| provenance | Every read in order, each marked recorded, live or blocked. |
| digestMatch | True only if the served stream and its order match exactly, with zero misses — byte-exact reproduction. |
| allRecordedConsumed | Whether every recorded value was used. False means the candidate skipped a step the original took. |
replayEvents() takes an array of events instead of a run id, so it needs no network at all. Use it in unit tests to pin agent behaviour against a fixture.deep_replay entitlement: GET /api/runs/:id/cassette returns 403 on other plans. Time-travel and step replay are available on every plan.A tool that runs live on a miss will have real side effects. Pass onMiss to intercept divergences — log them, block them, or fail the replay — if your tools are not safe to re-run.
Policies
A policy is a JSON array of rules evaluated against every run. A rule is either assert (a predicate that must always hold) or require (an antecedent when that, if it fires, makes a consequent then mandatory) — both block the run if violated. Predicates read tool calls, their arguments, and input/output text — never a fuzzy judge, an exact and repeatable verdict.
[
{
"id": "no-large-disputed-refund",
"kind": "require",
"description": "Disputed refunds over $100 must be escalated, not auto-issued",
"when": {
"op": "and",
"all": [
{ "op": "tool_arg", "tool": "issue_refund", "path": "amount", "cmp": "gt", "value": 100 },
{ "op": "input_matches", "pattern": "disputed" }
]
},
"then": { "op": "tool_called", "tool": "escalate_to_human" }
}
]
Rule fields:
| Field | Values | Description |
|---|---|---|
| kind | require / assert | require = block the run if triggered. assert = flag a violation but let the run continue. |
| on | tool_call / llm_call / run_end | Which event type triggers evaluation. |
| when | JSON condition object | Field path, operator, and value. Supports and / or / not nesting. |
| action | block / flag / alert | block stops execution. flag records a violation. alert also fires your alert rules. |
Operators: eq ne gt gte lt lte contains starts_with exists
Coverage gaps
A deterministic gate only catches what you've written a rule for. Coverage-gap analysis answers the honest follow-up question — which tools has nothing written a rule for at all — by scanning the last 90 days of real tool calls and flagging every one that no active rule's tool_called or tool_arg predicate even names. Not "a rule exists and didn't fire": there is nothing that could have blocked a bad call to it, even in principle. Ranked by call volume, so the biggest live blind spot sorts to the top.

Evals & CI gate
CommunityEvals test your agent on a fixed dataset. The CI release gate fails a build if a new model or prompt causes a regression against your golden tests.

# .github/workflows/ci.yml
- name: Runback eval gate
env:
RUNBACK_API_KEY: ${{ secrets.RUNBACK_API_KEY }}
run: |
# Run the eval — this blocks until scoring finishes and returns its id.
EVAL_ID=$(curl -fsS -X POST https://runback.dev/api/evals \
-H "authorization: Bearer $RUNBACK_API_KEY" \
-H "content-type: application/json" \
-d '{"dataset_id":"YOUR_DATASET_ID"}' | jq -r .eval_id)
# Ask for the release-gate verdict and fail the build if it did not pass.
# verdict is "pass" | "warning" | "fail" | "no_data".
curl -fsS "https://runback.dev/api/evals/$EVAL_ID/gate" \
-H "authorization: Bearer $RUNBACK_API_KEY" \
| jq -e '.verdict == "pass"'pass, warning, fail, or no_data, measured against your org's thresholds over trusted items only — production-captured runs and human-approved scenarios. The gate is most powerful when seeded by production incidents — see Golden corpus below.Golden corpus
GrowthThe golden corpus automatically mines your production incident runs into regression tests. Every run that triggered a policy violation or was marked as a failure is surfaced for review and approval as a golden test.

Auto-suggested rules
An uncovered incident shouldn't depend on someone remembering to go write a rule for it. When a still-open golden entry traces back to a specific tool call that threw, Runback drafts a candidate policy rule right there — a starting point for review, never something applied automatically. It's stated plainly when the draft is blunt: the rule language has no predicate for "only when it errors like this one did," so the draft blocks every future call to that tool unconditionally until a human narrows it.

Prompts
GrowthA versioned registry for the prompts your agents run — never a string hardcoded in your codebase. Every save is a new immutable version; a label (production, staging, or anything you name) points at the version your agents actually fetch, so you can edit and test without touching what's live.

{ role, content } messages with {{variables}} — and save it under a name.production label to the version you approved. Fully audited.production label requires the admin role, enforced server-side — every other role can save a new version but can't promote it live.Approvals
StarterA human-in-the-loop review queue for high-stakes agent decisions. There's no policy-rule field that routes a decision here automatically — it's explicit: your own agent code calls the API at the point you want a human to sign off, typically right before a policy-flagged action would otherwise run.
Your call to POST /api/approvals creates a pending approval. Approvers (team members with the approve permission) see it in Approvals and can approve or reject with a note. The decision is sealed into the run record.
Every approval decision (who decided, when, with what note) is appended to the run's tamper-evident record. The chain covers both the agent's decision and the human decision.
// Request approval from your own agent code, at the point you want a human decision
POST /api/approvals
{
"run_id": "run_abc123",
"policy_name": "no-large-disputed-refund",
"rule_desc": "Disputed $250 refund — requires senior review"
}
// Get pending approvals
GET /api/approvals?status=pendingAnomalous — flagged for review
Deliberately separate from the deterministic gate above: a statistical-outlier score is a judgment call, and a judgment call should never be the thing that blocks a run. This section scores each tool's recent calls against that same tool's own history — a numeric argument more than 3 standard deviations from baseline gets surfaced here, nothing more. No rule matched, nothing was blocked; it's a review signal, not a verdict. Tools with no policy coverage at all sort first, since an anomaly on a tool nothing else is watching is the bigger gap.

Incidents
GrowthAn incident is a structured record of an agent failure or policy breach — separate from the run itself. Incidents track status (open → investigating → resolved), timeline, and linked runs.

| Status | Meaning |
|---|---|
| open | Incident created, not yet assigned or being investigated |
| investigating | A team member is actively looking into the root cause |
| resolved | Root cause identified, fix deployed, golden test added |
// Create an incident from a run
POST /api/incidents
{
"run_id": "run_abc123",
"title": "Refund agent bypassed $100 limit",
"severity": "high"
}
// Update status
PATCH /api/incidents/:id
{ "status": "resolved", "resolution": "Policy updated, golden test enrolled" }From any run detail page, use the Open incident button to create a linked incident in one click. The incident page shows the full run, the policy violation, the approval history, and the timeline.
Alerts
GrowthAn alert rule watches the fleet and fires the moment something crosses a line, so the first you hear of a bad deploy is not a customer. Rules are evaluated against captured runs, not sampled metrics.
| Trigger | Fires when |
|---|---|
| Run failure | Any run ends in error. |
| Error rate | The failure rate over a rolling window crosses your threshold. |
| Cost spike | Spend over a rolling window crosses a dollar threshold. |
Each rule routes to email, Slack, or any webhook — PagerDuty and Opsgenie both accept one. Deliveries are deduped, so one incident does not become a hundred pings, and the Alerts page shows the last delivery per rule including failures, so a webhook that has quietly stopped accepting posts is visible rather than assumed healthy.
Inter-agent trust chain
ProIn multi-agent systems, a subagent receiving instructions from an orchestrator has no cryptographic proof the orchestrator is who it claims to be, or that the scope of the delegation hasn't been widened in transit. Runback's trust fabric seals every delegation edge.
Every orchestrator→subagent call produces a signed attestation — Ed25519 wherever this deployment has a keypair configured, the same signature the per-run audit record uses, HMAC-SHA256 fallback otherwise. The signed payload carries the calling agent, called agent, depth, permitted scope, and a hash of the parent attestation.
The full chain from root to leaf exports as a signed artifact. POST it to /api/trust/verify — or re-derive the signature yourself against our published key. No Runback account needed.
// Issue a delegation token (orchestrator → subagent)
POST /api/trust/chain
{
"parent_run_id": "run_orchestrator_abc",
"child_agent": "kyc-subagent",
"scope": "read:customer",
"depth": 1
}
// Verify the full chain
POST /api/trust/verify
{
"chain": [ /* array of attestation tokens */ ]
}
// Response: { valid: true, depth: 2, root: "loan-orchestrator", ... }AUDIT_ED25519_PRIVATE_KEY where configured — asymmetric, independently verifiable offline against the published public key — HMAC-SHA256 via AUDIT_SIGNING_KEY as a fallback. Self-hosted deployments need at least one of the two set.Vercel AI SDK
CommunityThe deepest integration — full context capture, in-process PII redaction, step replay, and typed tool wrappers. About 3 lines of change to an existing agent.
import { withDebugger } from "@runback/sdk";
import { generateText, stepCountIs } from "ai";
// 1. Wrap your model and tools
const dbg = withDebugger(model, {
runName: "customer-support",
apiKey: process.env.RUNBACK_API_KEY,
redact: "standard", // false | "standard" | "strict"
tags: { env: "prod" },
});
// 2. Use dbg.model and dbg.tools — drop-in replacements
const result = await generateText({
model: dbg.model,
tools: dbg.tools(myTools),
stopWhen: stepCountIs(10),
prompt: task,
});
// 3. Finish the run
await dbg.finish({ output: result.text, status: "success" });
// On error:
// await dbg.finish({ status: "error", error: err });Redaction levels:
| Level | What it strips |
|---|---|
| none | Nothing — full fidelity capture |
| standard | Email, SSN, credit card numbers, and API keys/secrets (Anthropic, OpenAI, Groq, Stripe, GitHub, Slack, Google, AWS, JWTs) — high-confidence patterns only |
| strict | All of standard + phone numbers and IPv4 addresses — noisier patterns, more false positives |
customPatterns or a full redactor callback in the SDK config.Manual recording (any agent loop)
CommunityNot on the Vercel AI SDK? startRun() records the same spans from any TypeScript or JavaScript agent loop — a raw provider SDK, your own orchestration, anything. Nothing is wrapped; you call it where the work happens.
import { startRun } from "@runback/sdk/core";
const run = startRun({ runName: "support-agent", input: task, redact: "standard" });
// Record each model call
run.llm({
model: { provider: "openai", model_id: "gpt-4o" },
request: { system: systemPrompt, messages },
response: { text: completion, finish_reason: "stop" },
usage: { input_tokens: 412, output_tokens: 88, total_tokens: 500 },
latencyMs: 940,
});
// …and each tool call
run.tool({ toolName: "lookup_customer", toolCallId: "t1", input, output, latencyMs: 44 });
// Optional: capture intermediate reasoning
run.reasoning("Disputed charge over the limit — escalating");
await run.finish({ output: answer, status: "success" });Go (preview)
CommunityThe Go SDK ships in the Community repository at packages/sdk-go. It is not yet published as a Go module — go get github.com/letsRunback/runback-go will not resolve — so vendor it or add a replace github.com/letsRunback/runback-go => ./packages/sdk-go to your go.mod for now. It covers event capture, redaction, and ingest — the same manual-recording shape as above, called from a Go agent loop. Redaction is on by default (standard tier): emails, SSNs, credit cards, private key blocks, and common provider API keys/tokens are scrubbed from event content before it's sent. Every run also gets a real cassette_digest, computed the same way as the TS/Python SDKs — checked byte-for-byte against the TS implementation in CI.
import "github.com/letsRunback/runback-go/runback"
run := runback.NewRun(runback.Options{RunName: "support-agent", Input: task})
run.LLM(runback.LlmInput{
Model: runback.Model{Provider: "openai", ModelID: "gpt-4o"},
Request: runback.LlmRequest{Messages: []runback.ModelMessage{{Role: "user", Content: task}}},
Response: runback.LlmResponse{Text: runback.Ptr(completion), FinishReason: runback.Ptr("stop")},
Usage: &runback.TokenUsage{InputTokens: 412, OutputTokens: 88, TotalTokens: 500},
LatencyMs: runback.Ptr(940),
})
run.Tool(runback.ToolInput{ToolName: "lookup_customer", ToolCallID: "t1", Input: input, Output: output})
run.Finish(runback.FinishInput{Output: answer, Status: "success"})OpenTelemetry (any framework)
CommunityAny framework that emits OpenTelemetry GenAI spans works with Runback. Set three environment variables and your traces flow in automatically.
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://runback.dev/api/otel/v1/traces
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/json
OTEL_EXPORTER_OTLP_TRACES_HEADERS=authorization=Bearer <RUNBACK_API_KEY>RUNBACK_API_KEY with the key from your dashboard. Self-hosted deployments use your own base URL instead of runback.dev.Gateway proxy — any language, no SDK
CommunityPoint your agent's API base URL at a local gateway process instead of the model provider. Calls flow through to the provider and are captured on the way — your agent's source does not change, and it needs no Runback library, so this is the path for languages the SDKs do not cover.
@runback/gateway is not a published package, so there is nonpm install for it: you clone runback-community and run it from the repo. It also needs a terminal and a long-running server process. "No SDK" means no library inside your agent — it does not mean no engineer.# record: calls flow to the provider and are captured on the way through
npx tsx node_modules/@runback/gateway/bin/gateway.ts \
--mode record --port 8888 \
--upstream https://api.openai.com \
--cassette baseline.cassette.json
# then point your agent at it — e.g. OPENAI_BASE_URL=http://localhost:8888Replay mode serves the recorded responses with no upstream at all, and exits non-zero if the agent diverges from the recording — which is what makes it usable as a CI gate for an agent written in any language.
npx tsx node_modules/@runback/gateway/bin/gateway.ts \
--mode replay --port 8888 --cassette baseline.cassette.jsonRUNBACK_GATEWAY_UPSTREAM_KEY and RUNBACK_GATEWAY_TOKEN to keep the real credential inside the gateway process — see Security, "Gateway credential isolation". Secrets are read from the environment, never from argv, because a process's command line is readable by anything else on the host.Python — OpenAI / Anthropic
CommunityUse OpenLLMetry (Traceloop) to auto-instrument the official Python SDKs.
pip install traceloop-sdkfrom traceloop.sdk import Traceloop
Traceloop.init(
api_endpoint="https://runback.dev/api/otel",
headers={"authorization": "Bearer " + os.environ["RUNBACK_API_KEY"]},
)
# All openai.chat.completions.create() and
# anthropic.messages.create() calls are now traced.LangChain, LangGraph, CrewAI, LlamaIndex
CommunityUse OpenInference or OpenLLMetry instrumentors, then point the OTLP exporter at Runback via the env vars above.
# LangChain / LangGraph
pip install openinference-instrumentation-langchain opentelemetry-exporter-otlp-proto-http
# LangChainInstrumentor().instrument()
# CrewAI
pip install traceloop-sdk
# Traceloop.init(...) — CrewAI spans flow straight in
# LlamaIndex
pip install openinference-instrumentation-llama-index
# LlamaIndexInstrumentor().instrument()OTEL_* environment variables in the same shell before running.OpenAI Agents SDK
CommunityThe Agents SDK has an official OpenTelemetry instrumentation package that converts its native trace data — agents, tools, generations, guardrails, handoffs — into GenAI semantic-convention spans. Point it at Runback with the same env vars as every other OTel source.
pip install openai-agents opentelemetry-instrumentation-openai-agents-v2from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.openai_agents_v2 import OpenAIAgentsInstrumentor
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter())) # reads OTEL_EXPORTER_OTLP_TRACES_* from env
OpenAIAgentsInstrumentor().instrument(tracer_provider=provider)
# Every Runner.run(...) call is now traced — including tool calls and handoffs.OTEL_* environment variables above first. The exporter reads them automatically — no endpoint/protocol arguments needed in code.Google Agent Development Kit (ADK)
CommunityADK emits standard OTLP spans natively — agent runs, tool calls, and model requests already follow the GenAI semantic conventions. No third-party instrumentor needed, just point the exporter at Runback.
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://runback.dev/api/otel/v1/traces
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/json
OTEL_EXPORTER_OTLP_TRACES_HEADERS=authorization=Bearer <RUNBACK_API_KEY>Environment variables
| Variable | Required | Description |
|---|---|---|
| RUNBACK_API_KEY | Yes | Your API key. All keys are prefixed rb_live_. |
| RUNBACK_INGEST_URL | Yes* | Where the SDK sends events — e.g. https://runback.dev (hosted) or your own origin (self-hosted). Defaults to http://localhost:3000, so *effectively required outside local dev — without it, events silently go nowhere. |
| RUNBACK_DEMO_MODE | No | Set to 1 to disable real model calls in replay/evals — used by this site's own public demo, and available on any deployment. |
Governance coverage
CommunityEvery other number here is measured over the agents that already report. Coverage is the one that measures what does not — because an agent nobody instrumented is the one most likely to be a problem, and it cannot show up in a dashboard built from its own telemetry.
You declare the estate: the agents that exist, who owns each, and how critical it is. Coverage then measures what is actually reporting against that register rather than against itself.
| State | Meaning |
|---|---|
| Under governance | Declared and reporting. The only state that counts as covered. |
| Not instrumented | Declared, never sent a run. The gap the register exists to surface. |
| Stopped reporting | Was reporting, has gone quiet. Silence is not health. |
| Undeclared | Sending runs but not on the register — nobody claimed ownership. |

Coverage feeds the compliance report directly: "we govern our AI estate" is a claim an auditor can check against a number, and the number is derived from your own declarations rather than from ours.
Model attribution & the upgrade gate
ScaleModel attribution mines your own run history for per-model error rate, latency and token cost, so an upgrade decision is made against your workload rather than a public benchmark. A run is attributed to the model of its first LLM call — the same rule cost attribution uses, so the two screens never disagree about which runs a model owns.
Every model name opens the runs behind its numbers.
Run your golden suite against a candidate model before it reaches production. A regression fails the gate, with the failing cases named.
Which agent classes behave differently across two models, with a severity and a plain-language reason for each divergence.
A model changing underneath you with no deploy on your side. Compares a current window against a historical baseline per agent.
Binary-search an ordered candidate list for the exact change that flipped a run — log₂ probes, each one recorded as proof of the search path.
Team & roles
ProOne governed workspace rather than a shared login. Roles decide who can change a policy, approve a decision, or seal a checkpoint.
| Role | Can |
|---|---|
| Owner | Everything, including billing and removing other owners. |
| Admin | Manage members, policies, alerts and checkpoints. Cannot remove an owner. |
| Member | Use the product: runs, replays, evals, approvals. |
| Viewer | Read-only. |
Every administrative act — a role change, a sealed checkpoint, an issued grant, a revoked session — is appended to a hash-chained admin audit log, visible under Activity. Who sealed a checkpoint is itself audit-relevant, so it is recorded with the same discipline as the decisions the product captures.
Fleet topology & benchmarks
ScaleThe Topology view renders your entire agent fleet as a live DAG — orchestrators at the top, subagents and tools at the leaves. Each node shows its call volume, error rate, and token spend. Click a node to drill into its runs.
Use topology to:
- Identify which subagent is the source of a systemic failure
- Spot unexpected delegation chains (orchestrator calling an agent it shouldn't)
- See token spend distribution across the fleet at a glance
Fleet benchmarks (Pro+)
The Benchmarks page compares your fleet's error rate, latency, and token spend against anonymised aggregate data from peer organisations in the same vertical. Percentile bands (p25, p50, p75, p95) let you see where your fleet stands relative to comparable deployments.

| Metric | What it shows |
|---|---|
| Error rate | % of runs ending in error vs. peer p50/p75 |
| Latency (p95) | Your 95th-percentile run duration vs. peer distribution |
| Token spend / run | Average tokens consumed per run vs. vertical peers |
| Policy block rate | % of runs blocked by governance rules vs. peer fleet |
Policy causes
ScaleThe Policy causes page shows a heat map of which policy × agent pairs produce the most blocks. It answers: “which agent is firing this rule most, and is it getting better or worse?”
| Column | What it shows |
|---|---|
| Block rate | Fraction of runs blocked by this policy/agent pair |
| Trend | ↑ worse / ↓ better vs. prior 30-day window |
| Total blocks | Absolute count over the selected window |
The heat map intensifies as block rate increases — zero is transparent, >20% is rose. Use it to identify policies that need rule refinement or agents that need prompt changes.
PII redaction
CommunityOn the Vercel AI SDK and Python/LangGraph collectors, redaction runs in-process — inside your application, before any data leaves to Runback; the raw values are never stored anywhere outside your process. Integrations that ship via OpenTelemetry redact server-side instead, after the raw spans reach us — see the note in OpenTelemetry for which integrations that applies to.
Standard redaction strips: email addresses, SSNs, credit card numbers, API keys, bearer tokens, and common secret patterns (Anthropic, OpenAI, Groq, Stripe, GitHub, Slack, Google, AWS, JWTs) — high-confidence, format-based matches. Strict adds phone numbers and IPv4 addresses, at the cost of more false positives.
Redacted values are replaced with a deterministic placeholder like [redacted:email] so the structure of the trace is preserved for replay.
Every string field is also capped at 50,000 characters (configurable via maxStringLength) before it's sent — a single oversized tool output or pasted document can't bloat an ingest payload. A capped field gets a …[truncated: N more chars] marker, and the run's metadata reports how many fields were capped (truncated_fields) — visible, not silent.
customPatterns or a full redactor callback via the SDK config.Cost attribution & chargeback
ScaleThe Cost page breaks down your AI spend by model and agent over any time window. The Cost → Teams view shows spend per team with budget tracking and alert thresholds.

Cost data is derived from captured token counts in every LLM span. No billing integration required.
Team chargeback (Enterprise)
Per-team budget caps and automated alerts when a team approaches or exceeds its monthly cap.
| Feature | Description |
|---|---|
| Per-team spend | Token cost attributed to each team over a rolling 30-day window |
| Budget caps | Set a monthly spend limit per team; alerts fire at 80% and 100% |
| Overage enforcement | Optional: block ingest for a team that exceeds its cap until the next month |
| CSV export | Export the period report for internal billing or departmental chargebacks |
Configure team budgets under Settings → Teams → Budget.
Signed audit records
CommunityEvery run can be exported as a signed audit record — a JSON bundle containing all spans, a SHA-256 hash chain over them (each event hashed over the previous, terminating in one content_digest), and a cryptographic signature over that digest. Signed with Ed25519 by default, so it's verifiable offline by anyone against Runback's published public key — no shared secret, no account, no trusting Runback's servers. Self-hosted instances that haven't set an Ed25519 key fall back to an HMAC-SHA256 signature, verifiable only with your own shared key. (The org-wide ledger those runs seal into separately carries a Merkle root over its checkpoints — see Security — a different construction from a single run's hash chain.) Verifiable independently at runback.dev/verify.
# Download via API
curl -H "Authorization: Bearer $RUNBACK_API_KEY" \
https://runback.dev/api/runs/{run_id}/audit > audit.json
# Verify locally
npx @runback/verify audit.jsonThe audit ledger
CommunityThe section above describes one run's signed record. The ledger is the org-wide version: every sealed run appended to a single append-only hash chain, where each entry carries the hash of the entry before it. Change, delete or insert any past decision and the chain stops reproducing — at the exact entry that changed, not merely somewhere.
Verification does not compare a stored hash against itself. It rebuilds each leaf from the run as it exists now and checks it still matches what was sealed. A run edited after the fact fails at its own position even though its stored hash is untouched.
Sealing a checkpoint signs the current head plus a Merkle root over every leaf up to it. The signature covers the tombstone set too, so a retention deletion cannot be used to quietly excuse an alteration until a fresh checkpoint re-anchors it.
Verify integrity on the Ledger page runs that check live and shows it happening: the chain re-derives entry by entry, then the head and Merkle root are matched against the last signed checkpoint. Nothing is pre-computed — if your ledger verifies in 80ms you see 80ms, and if it takes three seconds you see three seconds.

A break is the case the screen is designed for. The failing entry is marked at its exact sequence number with the run behind it, and everything after it greys out rather than turning red: past a broken link nothing has been established, and only the break itself is a finding.
| Verdict | Meaning |
|---|---|
| Chain intact | Every run re-derives to its sealed leaf, the links hold, and the head + Merkle root match the signed checkpoint. |
| Tamper detected | A specific entry no longer reproduces. The seq and run id are named; the ledger is the evidence, not the alarm. |
| Could not verify | The ledger or run store could not be read in full. Explicitly NOT a tamper finding — retry before drawing any conclusion. |
Self-attested is not the same as witnessed. A checkpoint signed only by this deployment proves nobody other than the operator altered the chain. Where an external time-stamp authority has attested a checkpoint, verification names it, and that head cannot have been re-sealed after the fact.
# The same check, over the API
curl -X POST -H "Authorization: Bearer $RUNBACK_API_KEY" \
https://runback.dev/api/app/ledger
# Streamed, one JSON object per line, as the walk happens
curl -N -X POST -H "Authorization: Bearer $RUNBACK_API_KEY" \
https://runback.dev/api/app/ledger/streamRegulatory dashboard & compliance export
EnterpriseThe Regulatory page maps your live run data to framework controls — EU AI Act Annex III, APRA CPS 230, and NIST AI RMF. Each control shows whether it's satisfied, partially covered, or missing evidence, based on your actual captures.
| Framework | Controls mapped |
|---|---|
| EU AI Act Art. 12 | Logging and traceability — tamper-evident decision record |
| APRA CPS 230 | Operational risk, continuous monitoring, incident management |
| NIST AI RMF | Govern, Map, Measure, Manage — live status per function |
| ISO/IEC 42001 | AI management system — evidence export for auditors |
Compliance export
The Compliance export generates a structured evidence package for auditor and regulator submissions — a signed, time-stamped summary of your governance posture, not a self-assessment. It includes:
- Policy inventory: all active rules, their versions, and block/flag counts over the audit period
- Incident log: every opened incident, its resolution, and the run record it links to
- Model change history: every model upgrade event and the CI gate result that preceded it
- Merkle-rooted ledger checkpoint: proof that the audit period record is complete and unaltered
Sealed AI narratives
EnterpriseAn AI-generated explanation — either a root-cause narrative for a model-diff divergence, or a compliance control's evidence summary — sealed the moment it's generated: hash-chained to your org's prior narratives and signed, with the exact evidence it was generated from pinned by digest. If that evidence changes afterward, re-verification catches it — the explanation is disposable, the proof it wasn't rewritten is not.

Two entry points, one sealing mechanism:
- “Explain this” on a model-diff divergence (Models → Diff) — a root-cause narrative for why behaviour changed between two models.
- “Explain this control” on any regulatory control (Regulatory page) — a plain-English summary of that control's live evidence.
Pairwise comparison & judge calibration
GrowthPairwise comparison judges two eval runs head-to-head, item by item — not two independent pass/fail scores. For each shared item, an LLM judge picks which output is better (or calls it a tie), with position-bias mitigation (the side order is randomized and un-swapped for storage) so the judge can't learn to favor “A.”

Judge calibration
Spot-check real (non-demo) LLM-judge verdicts against human review. Agree with the judge, or correct it — a correction becomes a few-shot example fed back into that rubric's future judging, so the judge gets better at exactly the cases it's been wrong about.

Auto-mined adversarial tests
ScaleA daily job goes further than mining the literal failure: it takes each real policy block or low-scoring eval and asks a model to probe the same weak spot from a different angle — a new phrasing, not the identical input — so your test suite covers the failure mode your production traffic actually found, not just the one exact case.

Candidates land in an “Auto-mined failures” dataset as pending items — never auto-approved. Review and approve each one in Datasets before it affects your release gate, same as any golden test.
External security findings
EnterpriseA narrowly-scoped, write-only key lets a guardrail vendor — Lakera, Cisco AI Defense, or similar — post findings about your runs into your own sealed record instead of staying siloed in their dashboard. Each finding is hash-chained and signed on arrival, independently of your run's own oracle chain, so a vendor's webhook can never retroactively alter a run's replay identity.

POST /api/security-findings.run_id show up as a pill in that run's header — everything else lives in the run's sealed record.External auditor & regulator grants
EnterpriseIssue a read-only, time-limited key scoped to specific runs — or all of them — and hand it to an auditor or regulator instead of a login. It downloads the exact same signed audit record your own team would see: byte-identical, not a summary.

Requirements
CommunityRunback is a standard Next.js app backed by a Postgres database. No exotic infrastructure.
| Component | Minimum | Recommended |
|---|---|---|
| Node.js | 20 LTS | 22 LTS |
| Postgres | 14 | 16 (Supabase, RDS, Cloud SQL) |
| RAM | 512 MB | 2 GB |
| CPU | 1 vCPU | 2 vCPU |
| Disk | 10 GB | 50 GB (depends on run volume) |
Docker Compose
CommunityThe fastest self-host path. Spins up the app and a local Postgres in two commands.
# 1. Clone the Community edition. Public, source-available, no request needed.
git clone https://github.com/letsRunback/runback-community.git
cd runback-community
# 2. Copy and fill in the env file (set AUDIT_SIGNING_KEY and JWT_SECRET at minimum)
cp .env.example .env && $EDITOR .env
# 3. Start
docker compose up -d
# App is now at http://localhost:3000Postgres is bundled — no external database required. For production, override POSTGRES_PASSWORD in .env and point NEXT_PUBLIC_APP_URL at your domain.
scheduler service comes up alongside web and runs the same 18 background jobs the hosted deployment runs on Vercel Cron (ledger sealing, retention, drift alerts, the guard kill-switch, …) — nothing extra to configure. docker compose logs -f scheduler shows each run.Environment variables (self-hosted)
Community| Variable | Required | Description |
|---|---|---|
| AUDIT_SIGNING_KEY | Yes | HMAC key for tamper-evident audit records. Generate: openssl rand -hex 32 |
| AUDIT_ED25519_PRIVATE_KEY | Recommended | Ed25519 key so audit records are verifiable offline by anyone, with no shared secret. Falls back to HMAC (not independently verifiable) if unset. Generate: openssl genpkey -algorithm ed25519 |
| JWT_SECRET | Yes | Signs PostgREST session tokens. Generate: openssl rand -hex 32. Also activates database-enforced tenant isolation (Postgres RLS) as a defense-in-depth layer under the app's own org filtering — docker-compose.yml passes it through as SUPABASE_JWT_SECRET automatically, nothing extra to set. |
| POSTGRES_PASSWORD | Yes | Postgres password. No default — must be set in .env before docker compose up. |
| AUTHENTICATOR_PASSWORD | Yes | PostgREST's own DB role password, separate from POSTGRES_PASSWORD. docker compose up hard-fails without it. Generate: openssl rand -hex 32 |
| SUPABASE_SERVICE_ROLE_KEY | Yes | PostgREST service-role JWT, signed with JWT_SECRET. docker compose up hard-fails without it — see .env.example for the one-line generation command. |
| CRON_SECRET | Yes | Bearer token the bundled scheduler service presents to the 18 scheduled jobs (retention, ledger sealing, drift, billing reconcile, newsletter, …) — see docs/SELF_HOSTING.md's "Scheduled jobs" section. docker compose up hard-fails without it. Generate: openssl rand -hex 32 |
| NEXT_PUBLIC_APP_URL | Yes | Your public base URL, used for magic-link emails, webhooks, and redirects. docker compose up hard-fails without it — e.g. http://localhost:3000 or https://runback.yourco.com |
| RUNBACK_LICENSE | Enterprise | License key for Enterprise features (SSO, fleet dashboard, etc.). |
| RUNBACK_SMTP_URL | Air-gapped | Send email through your own SMTP relay instead of Resend's REST API, which a network with no egress cannot reach. Standard connection URL — smtp://relay.internal:25 (no auth), smtp://user:pass@relay.internal:587 (STARTTLS), smtps://…:465 (implicit TLS). Takes precedence over RESEND_API_KEY when both are set. Without either, a self-hosted deployment prints sign-in links to the server log. |
| RUNBACK_OPENAI_BASE_URL | Air-gapped | Point the OpenAI provider at your own OpenAI-compatible endpoint (vLLM, Ollama, LiteLLM, Azure OpenAI) instead of api.openai.com — e.g. http://vllm.internal:8000/v1. Without it, replay and everything built on it (evals, golden suites, model diff, playground, judge calibration) cannot reach a model on a network with no egress. OPENAI_API_KEY becomes optional once this is set, because self-hosted inference commonly takes no auth. |
| RUNBACK_ANTHROPIC_BASE_URL | Air-gapped | Same, for the Anthropic provider. |
| RUNBACK_GROQ_BASE_URL | Air-gapped | Same, for the Groq provider. |
| RUNBACK_REPLAY_MODELS | Air-gapped | Comma-separated. Replaces the built-in replay allowlist entirely — the right choice on an air-gap, where every built-in is an unreachable option and the first is the default selection. Read from the environment only, never from a request: the allowlist is a security control precisely because a caller cannot extend it. |
| RUNBACK_EXTRA_REPLAY_MODELS | No | Comma-separated. Appends to the built-in allowlist instead of replacing it, for a deployment that can reach both its own models and the public APIs. |
| RUNBACK_ALLOW_PRIVATE_TARGETS | No | Reaching a private/RFC1918 SSO issuer, alert webhook, or SIEM collector is allowed by default self-hosted — nothing to set. Set to false only to opt into the stricter, hosted-style SSRF guard anyway. Always ignored on the hosted service. |
| RUNBACK_TSA_URLS | No | Internal RFC 3161 timestamp authority for a genuinely air-gapped deployment. Unset, ledger checkpoints are witnessed by two public authorities (freetsa.org, digicert) over the open internet — see Privacy posture in the self-hosting guide. |
| RUNBACK_DEMO_MODE | No | Set to 1 to disable real model calls in replay/evals. |
| RESEND_API_KEY | No | Resend API key for magic-link email auth. |
| OPENAI_API_KEY | No | Only needed for live step-replay & LLM eval judges. |
Upgrading
CommunitySQL migrations run automatically on startup — no manual steps required.
# Docker Compose
docker compose pull
docker compose up -d
# Migrations run automatically on boot.
# Downtime: typically under 5 seconds for minor releases.Authentication
All API requests require a Bearer token in the Authorization header.
curl -H "Authorization: Bearer rb_live_your_key" \
https://runback.dev/api/runsAPI keys are created in your dashboard under Settings → API Key (SDK). Keys are prefixed rb_live_.
Rotating a key
Keys are stored as SHA-256 hashes — we cannot show you an existing key again, only replace it. To rotate:
- Go to Settings → API Key (SDK) and issue a new key. The raw value is shown once.
- Deploy it to your agents as
RUNBACK_API_KEY. Both keys work during the changeover, so there is no ingest gap. - Revoke the old key from the same screen once no agent is using it.
Two scopes, chosen when you create the key. A telemetry-onlykey (the recommended option) can post runs and nothing else — it cannot read run content, so a leak cannot exfiltrate your traces. A full-accesskey drives the Bearer flows documented below — the CI gate, replay, and the read endpoints — and therefore CAN read run content, so treat it like a password and prefer telemetry-only wherever those flows are not needed. Compliance-read (rb_comp_) and SCIM (rb_scim_) keys are separate scopes and rotate the same way.
Roles
Every member of a workspace holds one of four roles. They are cumulative — each one can do everything the role below it can, plus more — and they are enforced server-side on every request, not in the interface. A session cookie and a full-access API key are both subject to the same check.
| Role | Can do |
|---|---|
| viewer | Read runs, traces, evals, policies, dashboards and audit records. Cannot change anything, and cannot trigger any action that spends money. |
| member | Everything a viewer can, plus the day-to-day work: replay a step, re-execute or bisect a run, write policies and prompts, create datasets, run evals, enrol golden cases, open and update incidents. |
| admin | Everything a member can, plus workspace administration: issue and revoke API keys, decide approvals, configure SSO, SIEM and model keys, place legal holds, issue external auditor grants, calibrate judges, and manage the subscription. |
| owner | Everything an admin can. The owner cannot be removed from the workspace and is the billing contact. |
memberrather than viewer: entitlement answers whether the workspace may use a feature, which is a different question from whether this person may spend on it.Roles are set when you invite someone (Settings → Team) and can be changed there afterwards. Invitations, role changes and removals are all written to the administrative audit log with the actor and source IP.
Runs API
| Method | Path | Description |
|---|---|---|
| GET | /api/runs | List runs. Query params: limit, offset, status, agent_name, from, to. |
| GET | /api/runs/:id | Get a single run with all spans. |
| POST | /api/runs/:id/replay | Trigger a step replay. Body: { span_id, model }. |
| GET | /api/runs/:id/audit | Download the signed audit record as JSON. |
| GET | /api/runs/:id/cassette | Download the re-executable cassette bundle. Enterprise — this is what replayRun() fetches. |
# List the last 10 failed runs
curl -H "Authorization: Bearer $KEY" \
"https://runback.dev/api/runs?status=error&limit=10"
# Download audit record
curl -H "Authorization: Bearer $KEY" \
"https://runback.dev/api/runs/run_abc123/audit" > audit.jsonPrompts API
GrowthThe one your agent's backend or CI actually calls: fetch whatever version a label currently points at, with plain HTTP caching so it works identically behind Docker Compose with no extra infra.
| Method | Path | Description |
|---|---|---|
| GET | /api/prompts/:name?label=production | Fetch the version a label points at. ETag + Cache-Control: max-age=60, stale-while-revalidate=300. |
| GET | /api/prompts | List every prompt's latest version in the org. |
| POST | /api/prompts | Save a new immutable version. Body: { name, template, model, variables?, commit_message? }. |
| GET | /api/prompts/:name/versions | List every version of one named prompt, newest first. |
| GET | /api/prompts/:name/labels | List every label currently set and the version it points at. |
| POST | /api/prompts/:name/labels | Move a label. Body: { label, version }. Moving "production" requires admin. |
| POST | /api/prompts/playground | Render a template with real values and run it against one or more models. Body: { template, variables, values, model, compare_model_ids? }. |
# What your agent's backend calls on every invocation
curl -H "Authorization: Bearer $KEY" \
"https://runback.dev/api/prompts/support-agent?label=production"
# Save a new version
curl -X POST -H "Authorization: Bearer $KEY" -H "content-type: application/json" \
https://runback.dev/api/prompts \
-d '{"name":"support-agent","template":[{"role":"user","content":"Hi {{name}}"}],"model":{"provider":"anthropic","model_id":"claude-sonnet-4-6"}}'Approvals & Incidents API
Starter| Method | Path | Description |
|---|---|---|
| GET | /api/approvals | List approvals. Query: status=pending|approved|rejected |
| POST | /api/approvals | Create an approval request for a run. |
| PATCH | /api/approvals/:id | Approve or reject. Body: { decision, note } |
| GET | /api/incidents | List incidents. Query: status=open|investigating|resolved |
| POST | /api/incidents | Create an incident linked to a run. |
| PATCH | /api/incidents/:id | Update incident status or resolution. |
Pairwise & calibration API
Growth| Method | Path | Description |
|---|---|---|
| POST | /api/evals/pairwise | Judge every shared item between two finished eval runs. Body: { eval_run_a_id, eval_run_b_id, randomize_order? }. Safe to re-run — already-judged items are skipped. |
| POST | /api/evals/pairwise/verdict | A human overrides one item's verdict. Body: { eval_run_a_id, eval_run_b_id, item_id, winner }. winner is a, b, or tie. |
| POST | /api/evals/calibrate | A human agrees with or corrects one judge verdict. Body: { review_id, human_passed, human_note? }. |
Audit API
Audit records are self-contained signed JSON bundles. To verify a record without trusting Runback:
# Verify with the CLI
npx @runback/verify ./audit.json
# Output: OK root=abc123... signature=valid spans=14The CLI recomputes the Merkle tree from the raw span data and checks it against the root in the record. On the hosted service, and any self-host with AUDIT_ED25519_PRIVATE_KEY set, the signature is Ed25519 — verifiable against Runback's published public key, with no shared secret and no account. A self-host that hasn't set that key falls back to an HMAC-SHA256 signature, verifiable only with your own key.
Narratives API
Enterprise| Method | Path | Description |
|---|---|---|
| POST | /api/runs/:run_id/narrative | Generate and seal a root-cause narrative for a model-diff divergence. Body: { model_a, model_b, window_days? }. Session-authenticated. |
| GET | /api/runs/:run_id/narrative | List sealed narratives for this run, each with a live re-verification verdict. Session-authenticated. |
| POST | /api/regulatory/:framework_id/:control_id/narrative | Generate and seal a compliance narrative for one control's live evidence. Session-authenticated. |
| GET | /api/regulatory/:framework_id/:control_id/narrative | List sealed narratives for this control, each with a live re-verification verdict. Session-authenticated. |
Security findings API
Enterprise| Method | Path | Description |
|---|---|---|
| POST | /api/security-findings | Ingest one finding. Body: { run_id?, span_id?, vendor, rule, severity, verdict, detail, raw_finding }. Authenticated with a security-findings-scoped key, not your ingest key. |
| GET | /api/runs/:run_id/security-findings | List a run's sealed findings, each with a live re-verification verdict. Session-authenticated. |
curl -X POST https://runback.dev/api/security-findings \
-H "Authorization: Bearer rb_secfind_your_key" \
-H "content-type: application/json" \
-d '{
"run_id": "run_abc123",
"vendor": "lakera",
"rule": "prompt-injection-detected",
"severity": "high",
"verdict": "flagged",
"detail": "Detected an embedded instruction overriding the system prompt.",
"raw_finding": { "score": 0.94 }
}'External grants API
Enterprise| Method | Path | Description |
|---|---|---|
| POST | /api/app/external-grants | Issue a grant. Body: { label, scope_type: 'run_ids'|'org_wide', run_ids?, expires_in_days }. Session-authenticated, admin+. |
| GET | /api/app/external-grants | List issued grants for the org — metadata only, never the raw key. |
| DELETE | /api/app/external-grants/:id | Revoke a grant immediately. |
The raw key returned from issuance authenticates directly on GET /api/runs/:run_id/audit as a Bearer token — no other endpoint accepts it.
Trust chain API
Pro| Method | Path | Description |
|---|---|---|
| POST | /api/trust/chain | Issue a delegation token from orchestrator to subagent. |
| POST | /api/trust/verify | Verify a trust chain. Returns: valid, depth, root, chain. |
Analytics & reporting API
ProRead-only, org-scoped reports — the same data the dashboard pages render, for pulling into your own BI, FinOps, or governance tooling. All accept a Bearer key.
| Method | Path | Description |
|---|---|---|
| GET | /api/cost/attribution | Cost by model and agent. Query: days (7–90, default 30). |
| GET | /api/chargeback | Per-team cost rollup with budget utilisation. Enterprise. |
| GET | /api/models/attribution | Per-model run counts, error rate, latency, tokens, trend. |
| GET | /api/models/diff | Compare two models over your runs. Query: modelA, modelB, days. Omit both to list models. |
| GET | /api/benchmark/fleet | Your metrics against anonymised fleet and vertical percentiles. |
| GET | /api/policy-causes | Which policies block which agents, and how often. |
| GET | /api/corpus/signals | Per-agent anomaly and error-rate signals. |
| GET | /api/golden/report | Golden-corpus coverage: cases enrolled, approved, blocking. |
| GET | /api/drift/report | Behavioural drift per agent vs the prior window. |
# 30-day cost by model, as JSON
curl -H "Authorization: Bearer $RUNBACK_API_KEY" \
"https://runback.dev/api/cost/attribution?days=30"Webhooks
StarterRunback can POST a JSON payload to any URL when a run ends or a policy is triggered.
| Event | When it fires |
|---|---|
| run.completed | Any run finishes (success or error) |
| run.error | A run ends with status = error |
| policy.block | A policy rule fires with action = block |
| policy.flag | A policy rule fires with action = flag |
| approval.created | An approval request is created |
| incident.opened | A new incident is opened |
// Example payload — policy.block
{
"event": "policy.block",
"run_id": "run_abc123",
"policy_id": "no-large-disputed-refund",
"tool": "issue_refund",
"blocked_at": "2026-07-01T14:22:01Z"
}Configure webhooks in your dashboard under Alerts → Webhooks. HMAC-SHA256 request signing is enabled by default.
Rate limits
| Limit | Community | Starter | Growth | Scale | Pro | Enterprise |
|---|---|---|---|---|---|---|
| Ingest — spans/sec | 50 | 200 | 500 | 1,000 | 2,000 | Custom |
| API reads — req/min | 60 | 300 | 600 | 900 | 1,200 | Custom |
| Replay — per hour | 10 | 50 | 100 | 200 | 500 | Custom |
| Eval runs — per day | 5 | 20 | 100 | 200 | 500 | Custom |
| Audit exports — per day | 10 | 50 | 200 | 500 | 1,000 | Custom |
429 Too Many Requests with a retry-after header. Per-request quota headers (X-RateLimit-*) are not currently sent — this previously said they were on every response, and no code emits them.