Docs Integrations
Manual recording
Record model and tool calls yourself, from any agent loop no integration fits: start a run, record each call, then finish it. About ten minutes.
Before you begin
- A Runback account and a Send only key. In the app, open Settings › Keys and access › API key, keep Send only selected, select Generate API key and copy it (it is shown once and starts
rb_cloud_). The quickstart walks through it. - Node.js 20 or later (
node --version).@runback/sdk/coreneeds no AI SDK and no model client.
Steps
Install the SDK
terminalShell npm install @runback/sdkExpected outputnpm finishes without errors.Set your key
terminalShell export RUNBACK_API_KEY="<RUNBACK_API_KEY>"Expected outputNothing is printed.echo $RUNBACK_API_KEYshould print your key.Record your agent's steps
run.llmandrun.toolare plain function calls. Call them from your framework's own hooks, or straight after a raw model API call. Redaction runs on what you hand them, in your process, before anything is sent. This example records one model call and one tool call; save it asagent.mjsand runnode agent.mjs.agent.mjsJavaScript // agent.mjs: npm install @runback/sdk (nothing else: @runback/sdk/core needs no AI SDK) import { startRun } from "@runback/sdk/core"; const run = startRun({ runName: "loan-agent", input: "Application A-1042" }); // Call these from your framework's own hooks, at the point each call happens. run.llm({ model: { provider: "anthropic", model_id: "claude-sonnet-4-6" }, request: { messages: [{ role: "user", content: "Assess application A-1042" }] }, response: { text: null, tool_calls: [{ tool_call_id: "t1", tool_name: "credit_check", input: { applicant: "A-1042" } }], finish_reason: "tool_use", }, usage: { input_tokens: 140, output_tokens: 22, total_tokens: 162 }, latencyMs: 640, }); run.tool({ toolName: "credit_check", toolCallId: "t1", input: { applicant: "A-1042" }, output: { score: 712 }, latencyMs: 85 }); await run.finish({ output: "approved", status: "success" });Expected outputNo[runback]line. A[runback]line means the run was not sent; see Troubleshooting.Check the run arrived
Open Runs in the app. Your run is at the top, named
loan-agent.
Troubleshooting
| You see | What it means | Fix |
|---|---|---|
[runback] no RUNBACK_API_KEY set — dropped 3 events | The key is not set in the terminal you ran the agent from. | Export RUNBACK_API_KEY in the same terminal (a new tab starts without it), then run the agent again. |
[runback] ingest failed 401 {"error":"Invalid API key"} | Runback rejected the key: mistyped, revoked or expired. | Copy the whole key again, or create a new one in Settings › Keys and access › API key. |
Failed to load the ES module (Node 22+) or Cannot use import statement outside a module (Node 20) | The file is not treated as an ES module. | Name it .mjs, or add "type": "module" to package.json. |
| The agent ran, but the run is not in Runs | The key belongs to a different workspace, or you are looking at the shared demo. | Check the workspace name in the top right of the app is the one the key came from. |
Your agent never fails because of Runback: if recording fails, the model call still returns and Runback prints one [runback] line saying why.
What Runback records
startRun from @runback/sdk/core, recorded by hand. 4 events, captured 19 Oct 2026.
Runstart · loan-agent
{ "schema_version": 1, "run_id": "01M59REJ9CTQ7A14RRY8KJG374", "span_id": "01M59REJ9CW3C6M1MBGXWG3T9S", "parent_span_id": null, "seq": 0, "ts_start": "2026-10-19T09:38:57.452Z", "ts_end": null, "type": "run", "phase": "start", "name": "loan-agent", "input": "Application from [redacted:email]", "output": null, "status": "running", "error": null, "metadata": {} }Modelclaude-sonnet-4-6 → credit_check
{ "ts_start": "2026-10-19T09:38:57.453Z", "ts_end": "2026-10-19T09:38:57.453Z", "model": { "provider": "anthropic", "model_id": "claude-sonnet-4-6" }, "request": { "system": null, "messages": [ { "role": "user", "content": "Assess the application from [redacted:email]" } ], "tools": [], "params": {} }, "response": { "text": null, "reasoning": null, "finish_reason": "tool_use", "tool_calls": [ { "tool_call_id": "t1", "tool_name": "credit_check", "input": { "applicant": "A-1042" } } ] }, "usage": { "input_tokens": 140, "output_tokens": 22, "total_tokens": 162 }, "latency_ms": 640, "error": null, "schema_version": 1, "run_id": "01M59REJ9CTQ7A14RRY8KJG374", "span_id": "01M59REJ9DP86N5V6KG7CE6G9Q", "parent_span_id": "01M59REJ9CW3C6M1MBGXWG3T9S", "seq": 1, "type": "llm" }Toolcredit_check
{ "schema_version": 1, "run_id": "01M59REJ9CTQ7A14RRY8KJG374", "span_id": "01M59REJ9D5MMBRRYM0CEWW7XC", "parent_span_id": "01M59REJ9DP86N5V6KG7CE6G9Q", "seq": 2, "ts_start": "2026-10-19T09:38:57.453Z", "ts_end": "2026-10-19T09:38:57.453Z", "type": "tool", "tool_name": "credit_check", "tool_call_id": "t1", "input": { "applicant": "A-1042" }, "output": { "score": 712 }, "latency_ms": 85, "error": null }Runend · loan-agent
{ "schema_version": 1, "run_id": "01M59REJ9CTQ7A14RRY8KJG374", "span_id": "01M59REJ9D4F81EA8M9RMAZ3WE", "parent_span_id": "01M59REJ9CW3C6M1MBGXWG3T9S", "seq": 3, "ts_start": "2026-10-19T09:38:57.453Z", "ts_end": "2026-10-19T09:38:57.453Z", "type": "run", "phase": "end", "name": "loan-agent", "input": null, "output": "approved", "status": "success", "error": null, "metadata": { "cassette_digest": "767663abb6d237e19a94322f081310dd9f4de9cfc0bbe9e4da2978a677672ae9", "cassette_entries": 2, "redaction_count": 2, "redaction_by_type": { "email": 2 }, "truncated_fields": 0 } }
The events this integration really sent, redacted in its own process, from a run in Runback's test suite. The model is scripted, so its name and its answers are the test's. The suite re-checks them on every change.
What it can and cannot do
Manual recording
| Question | Answer | Why |
|---|---|---|
| Code changes | One call per step | You call run.llm and run.tool yourself, from your own loop or your framework's hooks. |
| Context | What you pass | Runback records the request, response, tokens and latency you hand it. Nothing is read from your model client. |
| Redaction | In your process | On by default, before anything is buffered or sent. Pass redact: false to startRun to turn it off. |
| Stop before it runs | Yes, if you ask | Start the run with decide: true (or local enforce rules), then await run.decide(toolName, input) before the tool and run it only when .allowed is true. |
| Signed record | Yes | Each run is hash-chained and can be exported as a signed record. |
Without the SDK
POST the same events to /api/ingest with your key as a bearer token. The Start page's test run does exactly that, and the event fields are in Events. In Go, the Go SDK records the same way, by hand from your own loop.