Skip to main content
This integration will later be available as a Tuner library (or built into Tuner directly). For now, it’s a single drop-in file.

How it works

Building your VAPI assistants per call (the inline assistant parameter, customized per customer, campaign, or experiment)? Those transient assistants have no assistantId for Tuner’s native VAPI integration to import, so instead one function call in your end-of-call-report webhook pushes each finished call to Tuner. All of them report to one Custom API agent in Tuner: transcript, tool calls, per-turn voice metrics, recording, and cost. Attach each agent’s customization (system prompt, variant, use case) as metadata, and Tuner runs your evals based on it. The integration ships in two flavors, pick the one that matches your stack:

Node / TypeScript

send_to_tuner.ts, Node 18+ (uses the built-in fetch). No npm dependencies.

Python

send_to_tuner.py, Python 3.10+. One dependency: requests.
Both behave identically: the integration never throws (any failure is logged and your webhook handler is unaffected) and it’s idempotent, so re-sending the same call is harmless.

Setup

1

Create a Tuner account

Sign up at Tuner.
2

Create a "Custom API" agent

In Tuner, create a new agent and choose the provider Custom API. This is the agent your VAPI calls will show up under.
3

Add the file to your server

The whole integration is a single self-contained file with no dependencies beyond your language’s basics. Create send_to_tuner.ts (Node) or send_to_tuner.py (Python) next to your VAPI server and paste in the full source below, switch tabs for Node or Python and use the copy button in the code block’s top-right corner.
4

Get your keys and configure the file

Open the file and fill in the four config values at the top, all from the Tuner dashboard:
string
default:"https://api.usetuner.ai"
Your Tuner API base URL.
string
required
Found in Workspace Settings → API Keys (starts with tr_api_).
number
required
Found in Workspace → General Settings (a number).
string
required
On the Custom API agent: Agent Settings → Agent Connection → Agent ID.
Set the retry logic (just below the config) to taste, these control how the POST to Tuner behaves on transient failures:
Retries use exponential backoff with jitter and respect Retry-After; only transient failures are retried (other 4xx fail fast). The defaults are safe, raise MAX_ATTEMPTS if Tuner ingestion is occasionally slow on your network.
5

Call the function where your call ends

Call the forward function (sendCallToTuner in Node, send_call_to_tuner in Python) from inside your VAPI end-of-call-report webhook handler. The single required argument is the message object VAPI posts to your webhook (body.message).
Optional: attach your own metadata. Pass a second free-form object for anything that changes per call or per use case: customer names, the use case, A/B variant, prompt version, even your whole custom system prompt. It’s stored with the call in Tuner, and you can evaluate calls based on it.
Metadata must be JSON-serializable; if it isn’t, the call is still sent without it. Nothing is sent by default, omit the argument to send no metadata.To see how Tuner uses this metadata to evaluate each agent against its own instructions, read Evaluating Agents with Dynamic Instructions.
6

That's it

Make a call. When it ends, it appears under your Custom API agent in Tuner with the full transcript, tool calls/results, per-turn latency and interruption metrics, the recording, and cost, plus any metadata you attached.

Setting up simulation

Ingestion gets your real calls into Tuner, simulation lets Tuner place test calls to your VAPI agent and evaluate how it behaves. The integration already sends the sip_call_id Tuner needs to link a simulated call back to its simulation run, so once ingestion is working, simulation only needs a SIP trunk on the VAPI side.

Set up simulation for your VAPI agent

Create a free VAPI SIP trunk, link your assistant, and paste the SIP URI and credentials into Tuner, full walkthrough in the simulation setup guide.

Troubleshooting

Everything is logged with a Tuner: prefix, via console.warn / console.info in Node and the tuner logger (logging) in Python:
Success. (log-enriched) means precise per-turn metrics were attached.
This call was already ingested, safe to ignore.
The call log wasn’t usable; the call is still sent with payload-only metrics.
Tuner rejected the request with an error that retrying can’t fix (any 4xx except 408/429, e.g. a bad API key or wrong agent id), so it fails immediately without using any retry attempts. Check your config values.
A transient failure (network error, timeout, 408, 429, or 5xx) kept happening until all MAX_ATTEMPTS retries were used up. The call was not ingested.
If calls never arrive: double-check TUNER_API_KEY, TUNER_WORKSPACE, and TUNER_AGENT_ID, and confirm your handler only forwards end-of-call-report events.