Examples

Voice agent integrations

Connect Vapi, Retell AI, or OpenAI voice calls to Currai with server-side credentials, readable conversation evidence, and nested model traces.

These guides show how to connect a browser voice agent in your own application to Currai. Choose your provider:

Runnable reference implementations are available in the public curraiapp/examples repository. Each provider lives in an independent folder with its own environment template, lockfile, commands, tests, and README.

GuideUse it whenTranscript strategy
Vapi voice agentYour browser call uses @vapi-ai/webDisplay partial updates, but capture finalized transcript messages once
Retell AI voice agentYour application creates Retell web callsReconcile rolling updates, then prefer Retell's completed call transcript
OpenAI voice agentYour browser uses OpenAI Realtime WebRTC or TTSReconcile Realtime item events; capture TTS as a nested rendering step
Shell
git clone git@github.com:curraiapp/examples.git
cd examples

All three integrations follow the same Currai contract. The voice provider runs the live conversation. Your server sends a separate, best-effort record to Currai without changing the call outcome.

The fastest path is to let your coding agent inspect the application and wire the real voice-call boundary. From the root of your application repository, install the published Currai Skill:

Shell
npx skills add https://github.com/curraiapp/skills --skill currai

Configure the Currai credentials in the server environment first, then ask the coding agent:

Prompt
Use $currai to connect this Vapi, Retell AI, or OpenAI voice application to Currai using
the CURRAI_PUBLIC_KEY, CURRAI_SECRET_KEY, and CURRAI_BASE_URL already configured
in the server environment. Find and instrument the live conversation path, not
only the assistant or agent creation route. Keep capture best-effort, restart or
deploy the application, complete one real call, and verify the session, readable
User Story, and nested provider conversation and model events in Currai.

Tell the agent whether the application uses Vapi, Retell AI, or OpenAI and, in a monorepo, which package owns the browser call. The skill will:

  1. locate the provider's live call, transcript, error, and call-end handlers;
  2. add dependency-free native HTTP capture without installing a Currai SDK;
  3. keep Currai credentials, redaction, and retries in trusted server code;
  4. create one Currai session for each complete call and preserve parent-child event nesting;
  5. verify a real provider conversation instead of treating a standalone connectivity event as proof.

The provider guides below include ready-to-use prompts and the exact evidence that the skill should produce. Their manual sections are also the reference implementation when you want to review or customize the generated wiring.

Shared architecture

Prompt
Browser voice call
  ├── provider client or audio transport
  ├── live transcript state
  └── POST completed call to your server
                         │
Your server              ├── validate and redact
  ├── create Currai session
  ├── emit call boundary
  └── emit conversation turns
        └── nested model events

The browser may receive a provider's public or temporary call credential. Currai ingestion credentials must always remain on your server.

Configure Currai

Create an ingestion key from Workspace Settings → API Keys, then add these variables to your server environment:

Environment
CURRAI_PUBLIC_KEY=<your-currai-public-key>
CURRAI_SECRET_KEY=<your-currai-secret-key>
CURRAI_BASE_URL=https://www.currai.app

Do not use a browser-public prefix such as NEXT_PUBLIC_. Restart or redeploy the application after changing environment values.

Send native HTTP capture

You do not need a Currai SDK. Your server sends authenticated JSON to:

Prompt
POST /api/v1/capture-session
POST /api/v1/capture-event

Build HTTP Basic authentication only on the server:

TypeScript
function curraiAuthorization() {
  const publicKey = process.env.CURRAI_PUBLIC_KEY;
  const secretKey = process.env.CURRAI_SECRET_KEY;
  if (!publicKey || !secretKey) return null;

  return "Basic " + Buffer.from(`${publicKey}:${secretKey}`).toString("base64");
}

Use a short timeout and return a boolean rather than throwing into the product request:

TypeScript
async function sendToCurrai(path: string, body: unknown) {
  const authorization = curraiAuthorization();
  const baseUrl = process.env.CURRAI_BASE_URL;
  if (!authorization || !baseUrl) return false;

  try {
    const response = await fetch(`${baseUrl.replace(/\/$/, "")}${path}`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        authorization,
      },
      body: JSON.stringify(body),
      signal: AbortSignal.timeout(2_000),
    });
    return response.ok;
  } catch {
    return false;
  }
}

Before serialization, recursively redact object keys matching authorization headers, cookies, passwords, secrets, API keys, access tokens, refresh tokens, and bearer tokens.

Create the session first

Create one UUID session for one complete voice call. Await session capture before sending its first event:

TypeScript
const sessionAccepted = await sendToCurrai("/api/v1/capture-session", {
  session_id: sessionId,
  user_data: {
    user_id: userId,
    properties: { source: "browser-voice" },
  },
  metadata: {
    route: "browser-voice-call",
    environment: process.env.NODE_ENV ?? "development",
  },
  timestamp: startedAt,
  client_config: "currai-native-http",
});

if (!sessionAccepted) {
  return Response.json(
    { error: "Capture service unavailable." },
    { status: 503 },
  );
}

Do not emit events before this request succeeds. Otherwise, events may arrive without their session context.

Preserve readable conversation evidence

For every normalized turn, send a top-level agent event with recognized input and output fields:

TypeScript
{
  event_id: agentEventId,
  session_id: sessionId,
  parent_id: null,
  kind: "agent",
  primitive_name: "provider.conversation.turn",
  args: JSON.stringify({ input: userText }),
  result: JSON.stringify({ output: assistantText }),
  success: true,
}

Currai recognizes user evidence in fields such as args.input, args.prompt, and args.message. It recognizes assistant evidence in result.output, result.text, result.response, and result.answer.

A custom transcript property is trace metadata, not a replacement for these recognized fields. A session and trace can exist while the User Story remains empty if the top-level agent events do not carry recognizable conversation evidence.

Nest provider execution

Emit the model event as a child of the conversation turn:

TypeScript
{
  event_id: modelEventId,
  session_id: sessionId,
  parent_id: agentEventId,
  kind: "model",
  primitive_name: "provider.model",
  args: JSON.stringify({ input: userText }),
  result: JSON.stringify({ output: assistantText }),
  success: true,
  metadata: {
    provider: knownModelProvider,
    model: knownModelName,
  },
}

Only include provider and model values your application knows reliably. Do not infer them from a default when runtime routing can change them.

The expected event tree is:

Prompt
event: provider.voice_call

agent: provider.conversation.turn
└── model: provider.model

Stable retries

Generate sessionId, the call-boundary event ID, and child event IDs before the first upload. Build the completed payload once and reuse it on every retry.

This matters because Currai may accept the session and some events before the network fails. Fresh IDs would turn a retry into duplicate trace identities.

Retry temporary 429 and 5xx responses after short delays. Do not retry an unchanged invalid payload after a 400. Capture should always remain independent from the live call.

Definition of done

Complete one real provider call, then verify:

  1. The provider's call history contains the matching call.
  2. Currai Events contains one session and one call-boundary event.
  3. Each model event is nested beneath its conversation turn.
  4. Analyse → User Stories contains at least one visible user or assistant message.
  5. A failed provider call records success: false and a safe reason.
  6. A Currai failure does not interrupt or change the voice call.

Choose Vapi, Retell AI, or OpenAI voice for the provider-specific workflow.