> ## Documentation Index
> Fetch the complete documentation index at: https://docs.moda.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Vapi

> Using Moda with Vapi Voice AI

## Overview

Moda tracks your Vapi voice AI calls by processing end-of-call report webhooks. Each call is captured with full detail, including conversation turns, tool calls, squad transfers, call analysis, and cost breakdowns.

## Setup

<CodeGroup>
  ```bash Python theme={"dark"}
  pip install moda-ai
  ```

  ```bash Node.js theme={"dark"}
  npm install moda-ai
  ```
</CodeGroup>

## Usage

Point your Vapi webhook URL to your server and pass the payload to Moda:

<CodeGroup>
  ```python FastAPI theme={"dark"}
  from fastapi import FastAPI, Request
  import moda
  from moda import process_vapi_end_of_call_report

  app = FastAPI()

  moda.init("YOUR_MODA_API_KEY")

  @app.post("/webhooks/vapi")
  async def vapi_webhook(request: Request):
      payload = await request.json()
      process_vapi_end_of_call_report(payload)
      return {"status": "ok"}
  ```

  ```python Flask theme={"dark"}
  from flask import Flask, request
  import moda
  from moda import process_vapi_end_of_call_report

  app = Flask(__name__)

  moda.init("YOUR_MODA_API_KEY")

  @app.route("/webhooks/vapi", methods=["POST"])
  def vapi_webhook():
      payload = request.get_json()
      process_vapi_end_of_call_report(payload)
      return {"status": "ok"}
  ```

  ```typescript Express theme={"dark"}
  import express from 'express';
  import { Moda } from 'moda-ai';

  const app = express();
  app.use(express.json());

  Moda.init('YOUR_MODA_API_KEY');

  app.post('/webhooks/vapi', (req, res) => {
    Moda.processVapiEndOfCallReport(req.body);
    res.json({ status: 'ok' });
  });

  app.listen(3000);
  ```

  ```typescript Next.js theme={"dark"}
  import { Moda } from 'moda-ai';
  import { NextRequest, NextResponse } from 'next/server';

  Moda.init('YOUR_MODA_API_KEY');

  export async function POST(request: NextRequest) {
    const payload = await request.json();
    Moda.processVapiEndOfCallReport(payload);
    return NextResponse.json({ status: 'ok' });
  }
  ```
</CodeGroup>

## Supported Features

| Feature                 | Captured |
| ----------------------- | -------- |
| Conversation turns      | Yes      |
| Tool calls              | Yes      |
| Squad transfers         | Yes      |
| Call analysis / summary | Yes      |
| Structured data         | Yes      |
| Cost breakdown          | Yes      |
| Turn latency metrics    | Yes      |
| Recording URLs          | Yes      |

## Configuration

Use `ProcessVapiOptions` to customize the conversation and user identifiers:

<CodeGroup>
  ```python Python theme={"dark"}
  process_vapi_end_of_call_report(payload, {
      "conversation_id": "my_session_123",
      "user_id": "user_456",
  })
  ```

  ```typescript Node.js theme={"dark"}
  Moda.processVapiEndOfCallReport(payload, {
    conversationId: 'my_session_123',
    userId: 'user_456',
  });
  ```
</CodeGroup>

If not provided, `conversationId` defaults to `call.id` and `userId` defaults to `call.customer.number`.

## What Moda Captures

For each Vapi call, Moda captures a structured hierarchy of data:

```
Call
  |-- Turn 0          (first assistant turn)
  |-- Turn 1          (second assistant turn)
  |-- Tool Call        (e.g., lookupOrder)
  |-- Squad Transfer   (if applicable)
```

### Call-Level Data

| Field              | Description                                                |
| ------------------ | ---------------------------------------------------------- |
| Conversation ID    | Call ID or your custom conversation ID                     |
| User ID            | Customer number or your custom user ID                     |
| Duration           | Call duration in seconds                                   |
| Total cost         | Total call cost                                            |
| Ended reason       | Reason the call ended                                      |
| Assistant ID       | Vapi assistant identifier                                  |
| Status             | Call status (e.g., "ended")                                |
| Start / end time   | Timestamps for the call                                    |
| Summary            | AI-generated call summary                                  |
| Structured data    | Custom structured data from Vapi                           |
| Success evaluation | Success evaluation result                                  |
| Cost breakdown     | Separate costs for LLM, speech-to-text, and text-to-speech |

### Turn-Level Data

| Field              | Description                      |
| ------------------ | -------------------------------- |
| User message       | What the caller said             |
| Assistant response | What the assistant replied       |
| Model latency      | Time for the LLM to respond (ms) |
| Voice latency      | Time for voice synthesis (ms)    |
| Total latency      | End-to-end turn latency (ms)     |

## Troubleshooting

**No data appearing?**

* Ensure `Moda.init()` is called before processing webhooks
* Call `Moda.flush()` before your process exits to ensure all data is sent
* Verify your Vapi webhook is configured to send `end-of-call-report` events

**Call appears with no turns?**

* Moda handles both the `message`-wrapped format (real Vapi webhooks) and the legacy flat format
* If you see a call with no turns, check that your payload contains conversation data in `call.artifact.messages` or a `transcript` array

**Transcript not captured?**

* Vapi uses `[{role, message}]` format for transcripts. Moda automatically normalizes this for display.

<Info>
  For full SDK documentation, see the [Python SDK](/ingestion/moda-sdk) or [Node.js SDK](/ingestion/moda-sdk-node) guides.
</Info>
