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

# Post-call Extraction

> Pull structured fields from completed call transcripts with an LLM-driven analyzer

# Post-call Extraction

Post-call extraction lets you define a Custom Analysis schema (a free-form prompt + typed keys) once on the agent. After every completed call, the platform runs a structured-output LLM over the transcript and ships the extracted values inline in the same `session.completed` webhook — no second webhook to handle.

<Info>
  **Partner API (v2) only** for the endpoints below.
</Info>

## 1. Define the Custom Analysis schema

```bash theme={null}
PUT /api/v2/agent/{id}
Authorization: Bearer rg_live_...
Content-Type: application/json

{
  "extraction_config": {
    "prompt": "Analyze the conversation and extract the caller's intent, sentiment, and any commitments made.",
    "keys": [
      { "name": "interested", "type": "boolean", "description": "Did the user express interest in our offering?" },
      { "name": "callback_at", "type": "datetime", "description": "ISO timestamp if the user requested a callback; null otherwise." },
      { "name": "outcome", "type": "string", "description": "One of: callback_requested, sale_made, not_interested, voicemail, no_answer." }
    ],
    "includeToolLogs": false
  }
}
```

Pass `extraction_config: null` to **remove** the schema entirely.

### Enable / disable without losing your schema

Automatic extraction runs after a completed call only when the agent has a config
**and** the `extraction_enabled` master switch is on. The flag defaults to `true`,
so configured agents extract automatically out of the box.

To pause automatic extraction while keeping your prompt and keys intact, set
`extraction_enabled: false` instead of nulling the config:

```bash theme={null}
PUT /api/v2/agent/{id}
Authorization: Bearer rg_live_...
Content-Type: application/json

{ "extraction_enabled": false }
```

| Field                                | Effect                                                                |
| ------------------------------------ | --------------------------------------------------------------------- |
| `extraction_config: null`            | Removes the schema — prompt and keys are deleted.                     |
| `extraction_enabled: false`          | Keeps the schema; pauses **automatic** extraction after calls.        |
| `extraction_enabled: true` (default) | Runs automatically after every completed call (when a config exists). |

<Note>
  Manual replay (`POST /api/v2/calls/{call_id}/extract`) ignores `extraction_enabled` —
  it runs as long as a config exists. So you can keep iterating on a config with
  `dry_run` replays while automatic extraction stays paused.
</Note>

The current `extraction_enabled` value is returned on the agent object from
`GET /api/v2/agent/{id}`.

### Supported types

| Type       | Maps to            | Notes                  |
| ---------- | ------------------ | ---------------------- |
| `string`   | text               | Default.               |
| `number`   | float              |                        |
| `integer`  | int                |                        |
| `boolean`  | true / false       |                        |
| `array`    | `string[]`         | v1: array of strings.  |
| `date`     | ISO 8601 date      | `YYYY-MM-DD`           |
| `datetime` | ISO 8601 timestamp | `YYYY-MM-DDTHH:MM:SSZ` |

Each configured key is returned in the extraction result. When a value cannot be found, Vocobase returns `null` for that key so partners receive a consistent object shape.

## 2. Receive results in the webhook

After every completed call where the agent has an `extraction_config`, Vocobase runs extraction and ships the result inline in `session.completed`:

```json theme={null}
{
  "event": "session.completed",
  "data": {
    "session_id": "...",
    "transcript": [...],
    "recording_url": "...",
    "variables": {...},
    "extraction": {
      "status": "success",
      "values": {
        "interested": true,
        "callback_at": "2026-05-01T15:00:00Z",
        "outcome": "callback_requested"
      },
      "model": "vocobase-managed",
      "extractedAt": "2026-03-15T14:32:09.840Z",
      "latencyMs": 1840,
      "attempts": 1
    }
  }
}
```

See [Webhook Payloads](/webhooks/payloads) for the full payload shape.

## 3. Read results via the API

```bash theme={null}
GET /api/v2/calls/{call_id}
```

Returns the same `extraction` object on `data.session.extraction`. Useful for partners that prefer polling over webhooks, or for backfilling values into a CRM after the fact.

## 4. Replay extraction against past calls

Edited the prompt or added a new key? Backfill any past session by replaying the extractor:

```bash theme={null}
POST /api/v2/calls/{call_id}/extract
Authorization: Bearer rg_live_...
Content-Type: application/json

{ "dry_run": false }
```

`dry_run: true` returns the values without persisting and without re-firing the partner webhook (useful for prompt iteration). The replay always uses the agent's **current** `extraction_config`, not the config at original-call time. Returns 400 when the agent has no `extraction_config` defined.

## 5. Auto-export every extraction to Google Sheets

Instead of (or alongside) handling the webhook, you can have Vocobase append each completed call's extraction to a Google Sheet automatically — via a **post-call lifecycle hook**, with no endpoint to host.

### Prerequisite: connect Google Sheets on the agent

The agent must have an **active** `google_sheets` integration bound to it:

```bash theme={null}
POST /api/v2/agent/{id}/tools/google_sheets
Authorization: Bearer rg_live_...
Content-Type: application/json

{ "connection_id": "conn_..." }
```

See [B2B2B Customers](/b2b2b-customers) for the hosted connect-link flow that issues a Google `connection_id`.

### Add the post-call hook

```bash theme={null}
POST /api/v2/agents/{agentId}/lifecycle-hooks
Authorization: Bearer rg_live_...
Content-Type: application/json

{
  "stage": "POST_CALL",
  "name": "Dump extraction to sheet",
  "toolSlug": "google_sheets",
  "toolFn": "append_extraction",
  "input": { "spreadsheet_id": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms" }
}
```

<Note>
  Lifecycle-hook body fields are **camelCase** (`toolSlug`, `toolFn`) — unlike the snake\_case agent fields elsewhere on this page. `append_extraction` is **POST\_CALL only**. The `spreadsheet_id` is the ID in the sheet URL: `docs.google.com/spreadsheets/d/`**`THIS_PART`**`/edit` (not the `#gid=...` tab). Add `"sheet_name": "Leads"` to target a specific tab — defaults to `Sheet1`.
</Note>

On every completed call, Vocobase appends one row:

```
[ timestamp, session_id, ...extraction values ]
```

Values follow your `extraction_config.keys` **order**, so columns stay stable across calls even when a value is `null`.

### Map columns explicitly

To control the column layout, pass `input.columns` on `append_extraction` — an ordered list of `{ header, value }` pairs. Each `value` is a template token (`{{extraction.<field>}}`, or a call-metadata field), and a standalone placeholder keeps its type (numbers stay numeric). This is what the dashboard's column-mapping UI generates.

```bash theme={null}
POST /api/v2/agents/{agentId}/lifecycle-hooks
Authorization: Bearer rg_live_...
Content-Type: application/json

{
  "stage": "POST_CALL",
  "name": "Mapped extraction export",
  "toolSlug": "google_sheets",
  "toolFn": "append_extraction",
  "input": {
    "spreadsheet_id": "1BxiMVs0...",
    "columns": [
      { "header": "Outcome",     "value": "{{extraction.outcome}}" },
      { "header": "Callback at", "value": "{{extraction.callback_at}}" },
      { "header": "Interested",  "value": "{{extraction.interested}}" },
      { "header": "Caller",      "value": "{{caller_phone}}" }
    ]
  }
}
```

A **header row** (the `header`s) is written once — only when the target tab is empty. Available call-metadata tokens: `{{session_id}}`, `{{caller_phone}}`, `{{callee_phone}}`, `{{duration_secs}}`, `{{ended_at}}`, `{{transcript_summary}}`. Omit `columns` to fall back to the default all-keys row shown above.

### Append a raw row

For full control with no header management, use `append_row` with a flat `values` array of templates:

```bash theme={null}
POST /api/v2/agents/{agentId}/lifecycle-hooks
Authorization: Bearer rg_live_...
Content-Type: application/json

{
  "stage": "POST_CALL",
  "name": "Append selected fields",
  "toolSlug": "google_sheets",
  "toolFn": "append_row",
  "input": {
    "spreadsheet_id": "1BxiMVs0...",
    "values": ["{{extraction.outcome}}", "{{extraction.callback_at}}", "{{extraction.interested}}"]
  }
}
```

Hook failures are logged to your integration call log and **never block** call completion or the `session.completed` webhook.

## Failure semantics

Extraction failures **never block webhook delivery**. The `session.completed` payload always ships, with `extraction.status` indicating outcome:

| Status    | Meaning                                                                |
| --------- | ---------------------------------------------------------------------- |
| `success` | Extraction completed; `values` is present and typed per `keys`.        |
| `failed`  | Extraction could not be completed; `error` field describes what broke. |
| `skipped` | No transcript available (e.g., no-answer call, instant disconnect).    |

Partners that need extracted values can branch on `status === 'success'`; partners that just need transcript + recording can ignore the field entirely.

## Next steps

<CardGroup cols={2}>
  <Card title="Pre-call Variables" icon="square-bracket" href="/pre-call-variables">
    Template the agent's prompt and greeting with per-call values via `{{name}}` placeholders.
  </Card>

  <Card title="Webhook Payloads" icon="bell" href="/webhooks/payloads">
    Full reference for the `session.completed` payload, including the `extraction` block.
  </Card>
</CardGroup>
