Docs Integrations
OpenAI
Wrap the OpenAI client, in TypeScript or Python, to record every chat.completions and responses call. About five 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). Python 3.9 or later (python3 --version). - An OpenAI API key.
Steps
Install the SDK and the OpenAI client
terminalShell npm install @runback/sdk openaiterminalShell pip install "runback-sdk[openai]"Expected outputThe install finishes without errors.Set your keys
Runback reads its key from
RUNBACK_API_KEY; the model client reads its own.terminalShell export RUNBACK_API_KEY="<RUNBACK_API_KEY>" export OPENAI_API_KEY="sk-..."Expected outputNothing is printed.echo $RUNBACK_API_KEYshould print your key.Wrap the client and run it
Wrap your existing client once; every call through it is recorded. Save this as
agent.mjsand runnode agent.mjsagent.pyand runpython agent.py.agent.mjsJavaScript // agent.mjs: npm install @runback/sdk openai import OpenAI from "openai"; import { startRun } from "@runback/sdk/core"; import { wrapOpenAI } from "@runback/sdk/openai"; const task = "Say hello in three words."; const run = startRun({ runName: "support-agent", input: task }); const openai = wrapOpenAI(new OpenAI(), run); // chat.completions and responses const res = await openai.responses.create({ model: "gpt-4o-mini", input: task }); await run.finish({ output: res.output_text, status: "success" }); console.log(res.output_text);agent.pyPython # agent.py: pip install "runback-sdk[openai]" import openai from runback import Collector from runback.openai import wrap_openai task = "Say hello in three words." run = Collector(run_name="support-agent", input=task) client = wrap_openai(openai.OpenAI(), run) # or openai.AsyncOpenAI() res = client.responses.create(model="gpt-4o-mini", input=task) run.finish(output=res.output_text) print(res.output_text)Expected outputYour agent's normal output and no[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
support-agent. Open it to step through what the agent did.
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. |
OpenAIError: Missing credentials | OPENAI_API_KEY (or your provider's key) is not set. | Run the agent with the provider key set, e.g. OPENAI_API_KEY=sk-... node agent.mjs. |
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.
| 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. |
ModuleNotFoundError: No module named 'runback' | The package went into a different Python environment. | Activate the virtual environment you installed into, or install with the same python you run the agent with: python -m pip install …. |
openai.OpenAIError: Missing credentials | OPENAI_API_KEY (or your provider's key) is not set. | Run the agent with the provider key set, e.g. OPENAI_API_KEY=sk-... python agent.py. |
| 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
Per call: messages, system prompt, tool definitions, parameters, response text, tool calls, finish reason, tokens and latency, streamed or not. Errors are recorded, then re-thrown. For streams, pass stream_options: { include_usage: true } or the stream carries no token counts.
wrap_openai on openai.OpenAI(), chat.completions, scripted model. 5 events, captured 9 Oct 2026.
Runstart · py-openai-chat
{ "error": null, "input": "weather for [redacted:email]", "metadata": {}, "name": "py-openai-chat", "output": null, "parent_span_id": null, "phase": "start", "run_id": "01M4G4WRPYVWDE9VFXK1TRYWBD", "schema_version": 1, "seq": 0, "span_id": "01M4G4WRPYVWDE9VFXK1TRYWBE", "status": "running", "ts_end": null, "ts_start": "2026-10-09T10:56:10.462Z", "type": "run" }Modelgpt-4o-mini → get_weather
{ "error": null, "latency_ms": 3, "metadata": { "framework": "openai" }, "model": { "model_id": "gpt-4o-mini", "provider": "openai" }, "parent_span_id": "01M4G4WRPYVWDE9VFXK1TRYWBE", "request": { "messages": [ { "content": "Weather in Paris? cc [redacted:email]", "role": "user" } ], "params": { "temperature": 0.2 }, "system": "You are a weather bot.", "tools": [ { "description": "Weather for a city", "name": "get_weather", "parameters": { "properties": { "city": { "type": "string" } }, "required": [ "city" ], "type": "object" } } ] }, "response": { "finish_reason": "tool_calls", "reasoning": null, "text": null, "tool_calls": [ { "input": { "city": "Paris" }, "tool_call_id": "call_25", "tool_name": "get_weather" } ] }, "run_id": "01M4G4WRPYVWDE9VFXK1TRYWBD", "schema_version": 1, "seq": 1, "span_id": "01M4G4WRQ706J0WTYTRYZV2J15", "ts_end": "2026-10-09T10:56:10.471Z", "ts_start": "2026-10-09T10:56:10.468Z", "type": "llm", "usage": { "input_tokens": 12, "output_tokens": 5, "total_tokens": 17 } }Toolget_weather
{ "error": null, "input": { "city": "Paris" }, "latency_ms": null, "output": { "city": "Paris", "forecast": "sunny" }, "parent_span_id": "01M4G4WRQ706J0WTYTRYZV2J15", "run_id": "01M4G4WRPYVWDE9VFXK1TRYWBD", "schema_version": 1, "seq": 2, "span_id": "01M4G4WRQ8Z2500Q02FQ3B3WEV", "tool_call_id": "call_25", "tool_name": "get_weather", "ts_end": "2026-10-09T10:56:10.472Z", "ts_start": "2026-10-09T10:56:10.472Z", "type": "tool" }Modelgpt-4o-mini
{ "error": null, "latency_ms": 2, "metadata": { "framework": "openai" }, "model": { "model_id": "gpt-4o-mini", "provider": "openai" }, "parent_span_id": "01M4G4WRPYVWDE9VFXK1TRYWBE", "request": { "messages": [ { "content": "Weather in Paris? cc [redacted:email]", "role": "user" }, { "content": [ { "input": { "city": "Paris" }, "toolCallId": "call_25", "toolName": "get_weather", "type": "tool-call" } ], "role": "assistant" }, { "content": [ { "output": { "city": "Paris", "forecast": "sunny" }, "toolCallId": "call_25", "type": "tool-result" } ], "role": "tool" } ], "params": {}, "system": "You are a weather bot.", "tools": [ { "description": "Weather for a city", "name": "get_weather", "parameters": { "properties": { "city": { "type": "string" } }, "required": [ "city" ], "type": "object" } } ] }, "response": { "finish_reason": "stop", "reasoning": null, "text": "It is sunny in Paris.", "tool_calls": [] }, "run_id": "01M4G4WRPYVWDE9VFXK1TRYWBD", "schema_version": 1, "seq": 3, "span_id": "01M4G4WRQAEXKRV66F0YVMZYZ8", "ts_end": "2026-10-09T10:56:10.474Z", "ts_start": "2026-10-09T10:56:10.472Z", "type": "llm", "usage": { "input_tokens": 20, "output_tokens": 8, "total_tokens": 28 } }Runend · py-openai-chat
{ "error": null, "input": null, "metadata": { "cassette_digest": "ded351c0b41d719589dcdf05f870d1037028220907a85c38cd55168480bce9f7", "cassette_entries": 3, "redaction_by_type": { "email": 3 }, "redaction_count": 3 }, "name": "py-openai-chat", "output": "It is sunny in Paris.", "parent_span_id": "01M4G4WRPYVWDE9VFXK1TRYWBE", "phase": "end", "run_id": "01M4G4WRPYVWDE9VFXK1TRYWBD", "schema_version": 1, "seq": 4, "span_id": "01M4G4WRQAEXKRV66F0YVMZYZ9", "status": "success", "ts_end": "2026-10-09T10:56:10.474Z", "ts_start": "2026-10-09T10:56:10.474Z", "type": "run" }
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.
wrapOpenAI on new OpenAI(), responses, scripted model. 5 events, captured 2 Nov 2026.
Runstart · support-agent
{ "schema_version": 1, "run_id": "01M6DEG69951BRBTJGZ62C7E1D", "span_id": "01M6DEG699VM3HRKT87ZCXQWM5", "parent_span_id": null, "seq": 0, "ts_start": "2026-11-02T06:17:44.489Z", "ts_end": null, "type": "run", "phase": "start", "name": "support-agent", "input": "Weather in Paris? cc [redacted:email]", "output": null, "status": "running", "error": null, "metadata": {} }Modelgpt-4o-mini → get_weather
{ "ts_start": "2026-11-02T06:17:44.491Z", "ts_end": "2026-11-02T06:17:44.496Z", "model": { "provider": "openai", "model_id": "gpt-4o-mini" }, "request": { "system": null, "messages": [ { "role": "user", "content": "Weather in Paris? cc [redacted:email]" } ], "tools": [ { "name": "get_weather", "description": "Weather for a city", "parameters": { "type": "object", "properties": { "city": { "type": "string" } }, "required": [ "city" ] } } ], "params": {} }, "response": { "text": null, "reasoning": null, "finish_reason": "tool_calls", "tool_calls": [ { "tool_call_id": "call_1", "tool_name": "get_weather", "input": { "city": "Paris" } } ] }, "usage": { "input_tokens": 12, "output_tokens": 5, "total_tokens": 17 }, "latency_ms": 5, "error": null, "metadata": { "framework": "openai" }, "schema_version": 1, "run_id": "01M6DEG69951BRBTJGZ62C7E1D", "span_id": "01M6DEG69GQMGMZH7DFVY6CCGX", "parent_span_id": "01M6DEG699VM3HRKT87ZCXQWM5", "seq": 1, "type": "llm" }Toolget_weather
{ "schema_version": 1, "run_id": "01M6DEG69951BRBTJGZ62C7E1D", "span_id": "01M6DEG69HFNT53A9PDG6PNVRG", "parent_span_id": "01M6DEG69GQMGMZH7DFVY6CCGX", "seq": 2, "ts_start": "2026-11-02T06:17:44.496Z", "ts_end": "2026-11-02T06:17:44.497Z", "type": "tool", "tool_name": "get_weather", "tool_call_id": "call_1", "input": { "city": "Paris" }, "output": { "city": "Paris", "forecast": "sunny" }, "latency_ms": null, "error": null }Modelgpt-4o-mini
{ "ts_start": "2026-11-02T06:17:44.496Z", "ts_end": "2026-11-02T06:17:44.497Z", "model": { "provider": "openai", "model_id": "gpt-4o-mini" }, "request": { "system": null, "messages": [ { "role": "user", "content": "Weather in Paris? cc [redacted:email]" }, { "role": "assistant", "content": [ { "type": "tool-call", "toolCallId": "call_1", "toolName": "get_weather", "input": { "city": "Paris" } } ] }, { "role": "tool", "content": [ { "type": "tool-result", "toolCallId": "call_1", "output": { "city": "Paris", "forecast": "sunny" } } ] } ], "tools": [ { "name": "get_weather", "description": "Weather for a city", "parameters": { "type": "object", "properties": { "city": { "type": "string" } }, "required": [ "city" ] } } ], "params": {} }, "response": { "text": "It is sunny in Paris.", "reasoning": null, "finish_reason": "completed", "tool_calls": [] }, "usage": { "input_tokens": 20, "output_tokens": 8, "total_tokens": 28 }, "latency_ms": 1, "error": null, "metadata": { "framework": "openai" }, "schema_version": 1, "run_id": "01M6DEG69951BRBTJGZ62C7E1D", "span_id": "01M6DEG69HP9SCZA801GKAGTJK", "parent_span_id": "01M6DEG699VM3HRKT87ZCXQWM5", "seq": 3, "type": "llm" }Runend · support-agent
{ "schema_version": 1, "run_id": "01M6DEG69951BRBTJGZ62C7E1D", "span_id": "01M6DEG69JQDF81QVJRQYGAQ6W", "parent_span_id": "01M6DEG699VM3HRKT87ZCXQWM5", "seq": 4, "ts_start": "2026-11-02T06:17:44.498Z", "ts_end": "2026-11-02T06:17:44.498Z", "type": "run", "phase": "end", "name": "support-agent", "input": null, "output": "It is sunny in Paris.", "status": "success", "error": null, "metadata": { "cassette_digest": "243799279d37043958e3c3b08ae9be720adcef70a75fce406151ce4455808d74", "cassette_entries": 3, "redaction_count": 3, "redaction_by_type": { "email": 3 }, "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
OpenAI, Python wrapper
| Question | Answer | Why |
|---|---|---|
| Context | Full | Every chat.completions and responses call, streamed or not, with tools and tokens. |
| Tool steps | Inferred | Read from the tool result in the next request, so a tool's latency isn't known. |
| Redaction | In your process | On by default. |
| Stop before it runs | No | The client never runs your tools, so there is no hook to block in. |
| Replay | Re-issue only | A model step can be re-sent to a live model. The whole run can't be re-run with the same tool results: they aren't kept. |
| Signed record | Yes | Each run is hash-chained and can be exported as a signed record. |