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

# Execution payload reference

> Every field in a Bolna call execution: status, transcript, extracted data, telephony, cost, usage, latency and retries.

An **execution** is the record of one call. The same payload is returned by
[Get execution](/docs/api-reference/executions/get_execution), pushed to your
[webhook](/docs/post-call/polling-call-status-webhooks), listed by
[Get agent executions](/docs/api-reference/executions/get_executions), and shown as *Raw Call Data* in
[Call History](/docs/agent-setup/call-history).

It carries the call record, the billing record and the diagnostic record in one object. Most integrations
need a dozen of its keys. This page groups them by what they are for.

<Note>
  List endpoints return a trimmed projection: `id`, `status`, `conversation_duration`, `created_at` and a
  short `telephony_data`. Fetch the execution by id, or receive it on a webhook, to get everything below.
  A payload can also contain keys that are not listed here; those are internal and carry no compatibility
  guarantee, so do not build on them.
</Note>

***

## Before you build on this

Three things account for most of the bugs people hit reading this payload.

### Nothing is final until the status is terminal

`conversation_duration`, `total_cost`, `telephony_data.recording_url`, `transcript` and `extracted_data`
are `null`, `0` or empty until the call finishes and post-processing runs. Read them earlier and you will
record zeros. See the [status lifecycle](/docs/api-reference/executions/get_execution#status-lifecycle).

### `status: "completed"` does not mean a conversation happened

`status` is derived from the telephony provider's call-lifecycle field, not from the hangup cause, so a
call the callee rejected can still arrive as `completed`. Check `conversation_duration > 0` before
treating a call as answered. `telephony_data.hangup_reason` and `hangup_provider_code` carry the real
ending.

### A webhook fires several times for one call

Bolna POSTs this payload as the call's status changes, so a single call produces multiple deliveries
carrying the same `id`. Make your handler idempotent and key it on `id`, or you will count the same call
more than once.

Together those three give the shape of a correct handler: ignore non-terminal deliveries, then decide
what actually happened before recording anything.

```python Handling a delivery theme={"system"}
TERMINAL = {"completed", "no-answer", "busy", "failed",
            "canceled", "stopped", "error", "balance-low"}

def handle(execution):
    if execution["status"] not in TERMINAL:
        return                                  # more deliveries are coming for this call

    telephony = execution["telephony_data"]

    # `completed` on its own is not enough; check that a conversation actually took place
    if execution["conversation_duration"] == 0:
        return record_no_conversation(
            execution["id"], telephony["hangup_reason"], telephony["hangup_provider_code"]
        )

    # the transcript is how you tell whether the caller ever spoke
    transcript = execution.get("transcript") or ""
    if not any(line.startswith("user:") for line in transcript.splitlines()):
        return record_agent_only(execution["id"])   # agent spoke, caller never did

    record_conversation(execution["id"], transcript, execution["extracted_data"])
```

<Note>
  `handle` is keyed on `execution["id"]`, so a repeated delivery for the same call overwrites rather than
  duplicates. That is what makes it safe against point 3.
</Note>

***

## A complete payload

A real completed call. The agent's welcome message played, the caller never spoke, and the agent hung up
after its silence timeout.

```json Completed execution theme={"system"}
{
  "id": "b7140255-af33-4608-8e97-04dd944b8e48",
  "agent_id": "5bc97541-e320-4d95-a3a5-242cfe45621d",
  "batch_id": null,
  "campaign_id": null,

  "status": "completed",
  "smart_status": null,
  "error_message": null,
  "conversation_duration": 28,
  "answered_by_voice_mail": null,

  "created_at": "2026-09-04T08:52:07.408883+00:00",
  "initiated_at": "2026-09-04T08:52:09.737874",
  "updated_at": "2026-09-04T08:52:59.320486+00:00",
  "scheduled_at": null,
  "rescheduled_at": null,

  "user_number": "+919876543210",
  "agent_number": "+918035739222",
  "provider": "vobiz",

  "transcript": "assistant: Hello, this is Sonam from Bolna's Community...\nassistant: Hello, are you still on the line?\n",
  "summary": null,
  "extracted_data": {},

  "telephony_data": {
    "duration": 28,
    "to_number": "+919876543210",
    "from_number": "+918035739222",
    "recording_url": "https://api.bolna.ai/recordings/call/b7140255-af33-4608-8e97-04dd944b8e48",
    "hosted_telephony": true,
    "provider_call_id": "a41e7c93-2b58-4e07-95d1-8f30c62b7a11",
    "call_type": "outbound",
    "provider": "vobiz",
    "hangup_by": "Caller",
    "hangup_reason": "inactivity_timeout",
    "hangup_provider_code": 4010,
    "ring_duration": null,
    "post_dial_delay": null,
    "to_number_carrier": null
  },

  "total_cost": 3,
  "cost_breakdown": {
    "platform": 1,
    "network": 0.25,
    "transcriber": 0.432,
    "synthesizer": 0.69,
    "llm": 0,
    "total_cost_to_deduct": 1.878,
    "llm_breakdown": { "conversation": 0, "summarization": 0, "hangup": 0, "routing": 0, "voicemail": 0 },
    "synthesizer_breakdown": { "conversation": 0.69, "welcome_message": 0 },
    "transcriber_breakdown": { "conversation": 0.432, "analytics": 0 },
    "transfer_cost": 0
  },

  "usage_breakdown": {
    "transcriber_provider": "deepgram",
    "transcriber_model": "nova-3",
    "transcriber_language": "en",
    "transcriber_duration": 28.199938,
    "synthesizer_provider": "elevenlabs",
    "synthesizer_model": "eleven_turbo_v2_5",
    "synthesizer_characters": 217,
    "voice_id": "IkSv4tkouLJ6kYsQA7XD",
    "llmTokens": 0,
    "llmModel": { "azure/gpt-4.1-mini": { "input": 0, "output": 0 } },
    "provider_source": { "llm": "bolna", "synthesizer": "bolna", "transcriber": "bolna" },
    "llm_usage_breakdown": {
      "conversation": { "model": "azure/gpt-4.1-mini", "provider": "azure", "input": 0, "output": 0 },
      "disposition": { "model": null, "input": 0, "output": 0, "cached_tokens": 0 }
    },
    "synthesizer_usage_breakdown": { "conversation_characters": 69, "welcome_message_characters": 148, "welcome_message_cache": false, "provider_connected": false },
    "transcriber_usage_breakdown": { "transcriber_duration": 28.199938, "provider_connected": false },
    "endpointing": 200,
    "incremental_delay": 300,
    "buffer_size": 220,
    "hangup_after_silence": 15,
    "check_if_user_online": true,
    "trigger_user_online_message_after": 10,
    "voicemail_detection_enabled": false
  },

  "latency_data": {
    "time_to_first_audio": 262.37671,
    "stream_id": 257.49268,
    "region": "in",
    "transcriber": { "time_to_connect": 17, "turns": [] },
    "llm": { "time_to_connect": null, "turns": [] },
    "synthesizer": { "time_to_connect": 88, "turns": [{ "turn": -1, "time_to_first_token": 110, "time_to_last_token": 311 }] },
    "rag": null,
    "interruption_stats": {
      "total_interruptions": 0,
      "user_interrupted_agent_count": 0,
      "agent_interrupted_user_count": 0,
      "user_speaking_ms": 0,
      "agent_speaking_ms": 0,
      "longest_agent_monologue_ms": 0,
      "talk_to_listen_ratio": null,
      "barge_in_recovery_rate": null,
      "barge_in_recovery_count": 0
    }
  },

  "tool_call_logs": [],
  "transfer_call_data": null,
  "context_details": {
    "recipient_phone_number": "+919876543210",
    "recipient_data": { "timezone": "Asia/Kolkata" }
  },
  "retry_count": 0,
  "retry_config": null,
  "retry_history": null
}
```

***

## Identity and linkage

| Field | Type | Notes |
| - | - | - |
| `id` | uuid | The execution id. Use it with [Get execution](/docs/api-reference/executions/get_execution) and [raw logs](/docs/api-reference/executions/get_execution_raw_logs). |
| `agent_id` | uuid | Agent that ran the call |
| `batch_id` | uuid \| null | Set for [batch](/docs/api-reference/batches/overview) calls |
| `campaign_id` | uuid \| null | Set for [workflow campaign](/docs/workflows/campaigns) calls |

## Status and lifecycle

| Field | Type | Notes |
| - | - | - |
| `status` | string | The status you should branch on. See the [full lifecycle](/docs/api-reference/executions/get_execution#status-lifecycle), and the warning above about `completed`. |
| `smart_status` | string \| null | An outcome read from the transcript by a model. `null` for zero-duration calls, so treat it as a hint on top of `status`, never a replacement. |
| `error_message` | string \| null | Populated on `error` and some `failed` calls |
| `conversation_duration` | number | Conversation seconds. `0` means no conversation took place, whatever `status` says. |
| `answered_by_voice_mail` | boolean \| null | Set when voicemail detection is enabled and fires |

## Timing

| Field | Type | Notes |
| - | - | - |
| `created_at` | timestamp | When the execution record was created |
| `initiated_at` | timestamp | When dialing actually started |
| `updated_at` | timestamp | Last write, normally the end of post-processing |
| `scheduled_at` | timestamp \| null | For scheduled calls |
| `rescheduled_at` | timestamp \| null | Set when the call was rescheduled |

<Note>
  `created_at` and `initiated_at` differ by the queue wait. Use `initiated_at` when measuring
  dial-to-answer behavior, and `created_at` when reconciling against the moment you made the request.
  Scheduled calls also use the statuses `scheduled` and `rescheduled`, which sit before `queued`.
</Note>

## Conversation content

| Field | Type | Notes |
| - | - | - |
| `transcript` | string | Newline-delimited `speaker: text`. Speakers are `assistant` and `user`. This is how you tell whether the caller ever spoke. |
| `summary` | string \| null | Populated only when the agent's `call_summary_enabled` toggle is on. Off by default. |
| `extracted_data` | object | Post-call [extractions](/docs/prompting/using-extractions), nested `Category` then `Extraction` then `{ subjective, objective, confidence, confidence_label, reasoning_subjective, reasoning_objective, validation }`. `{}` when the agent has no dispositions. |

## Telephony

All under `telephony_data`.

| Field | Type | Notes |
| - | - | - |
| `duration` | number | Call duration in seconds as reported by the provider. Compare with the top-level `conversation_duration`, which is the figure billing uses. |
| `to_number` / `from_number` | string | Also mirrored at the top level as `user_number` / `agent_number` |
| `recording_url` | string \| null | `null` if recording is off or the call never connected |
| `call_type` | string | `outbound` or `inbound` |
| `provider` | string | `twilio`, `plivo`, `exotel`, `vobiz`, `sip-trunk` |
| `hosted_telephony` | boolean | Whether the number is Bolna-hosted rather than your own |
| `provider_call_id` | string | The provider's id for the call. Quote this in a provider support ticket. |
| `hangup_by` | string | Which side ended the call, for example `Caller`, or the provider name when the platform ended it |
| `hangup_reason` | string | Normalized reason, for example `inactivity_timeout` |
| `hangup_provider_code` | integer \| null | The provider's raw code. Carries rejection detail that `status` can miss. See the warning above. |
| `ring_duration` | number \| null | Seconds spent ringing, when the provider reports it |
| `post_dial_delay` | number \| null | Dial to first ring, when the provider reports it |
| `to_number_carrier` | string \| null | Carrier lookup for the dialed number, when available |

## Cost

`total_cost` and `cost_breakdown` are the billing figures, reported **in cents**. The dashboard presents
the same numbers as credits. See [Call pricing](/docs/pricing/call-pricing).

| Field | Notes |
| - | - |
| `total_cost` | Total for the call |
| `cost_breakdown.platform` / `network` | Bolna platform fee and telephony |
| `cost_breakdown.transcriber` / `synthesizer` / `llm` | Per-component cost |
| `cost_breakdown.total_cost_to_deduct` | The wallet deduction for this call. It can differ from `total_cost`, so reconcile billing against this field. |
| `cost_breakdown.llm_breakdown` | LLM cost split by purpose: `conversation`, `summarization`, `hangup`, `routing`, `voicemail`. A call with extractions bills more than one LLM pass. |
| `cost_breakdown.synthesizer_breakdown` | Splits `conversation` from `welcome_message`, which is cached separately |
| `cost_breakdown.transcriber_breakdown` | Splits `conversation` from `analytics` |
| `cost_breakdown.transfer_cost` | Added when the call was transferred |

## Usage

`usage_breakdown` is the unit-level detail behind the cost. Read it when you want to know *why* a call
cost what it did, or which models actually ran.

| Field | Notes |
| - | - |
| `transcriber_provider` / `transcriber_model` / `transcriber_language` | The transcriber that ran |
| `transcriber_duration` | Seconds of audio transcribed, fractional |
| `synthesizer_provider` / `synthesizer_model` / `voice_id` | The voice that ran |
| `synthesizer_characters` | Characters synthesized |
| `llmModel` / `llmTokens` | Model used and total tokens |
| `llm_usage_breakdown` | Per-purpose token detail for `conversation`, `disposition` and `summarization`, each with `input` and `output` |
| `provider_source` | Whether each component billed to `bolna` or to your own connected key |
| `synthesizer_usage_breakdown` | Splits `conversation_characters` from `welcome_message_characters`, and reports whether the welcome message was served from cache |
| `transcriber_usage_breakdown` | Transcribed duration, and whether your own key served the call |
| `endpointing`, `incremental_delay`, `buffer_size`, `hangup_after_silence` | The latency-relevant settings this call ran with. Useful when comparing two calls that behaved differently. |
| `check_if_user_online`, `trigger_user_online_message_after`, `voicemail_detection_enabled` | The presence-detection settings this call ran with |

## Latency

`latency_data` is the diagnostic record. See [Call latencies](/docs/concepts/call-latencies) for how to read it.

| Field | Notes |
| - | - |
| `time_to_first_audio` | Milliseconds from the end of the caller's utterance to the start of the agent's audio. The number callers actually feel. |
| `region` | Region the media ran in |
| `transcriber` / `llm` / `synthesizer` | Each has `time_to_connect` and a `turns` array of per-turn timings |
| `interruption_stats` | Barge-in behavior: counts in each direction, speaking time each side, `longest_agent_monologue_ms`, `talk_to_listen_ratio` and barge-in recovery. The first place to look when an agent talks over people. |
| `rag` | Knowledge-base retrieval timings, when the agent uses one |
| `routing` | Semantic-routing timings, when the agent uses routes |

<Warning>
  If PII masking is enabled on your account, phone numbers, `telephony_data.recording_url` and the string
  values inside `extracted_data` come back as `null`. The keys are still present, so code that reads them
  sees `null` rather than a missing field. Check with your account settings before building on those
  values.
</Warning>

## Transfers, context and retries

| Field | Notes |
| - | - |
| `tool_call_logs` | Always returned as an empty array. Per-invocation [function tool](/docs/tool-calling/custom-function-calls) detail is not included in API responses; read it from the execution in the dashboard instead. |
| `transfer_call_data` | Populated when the call was [transferred](/docs/tool-calling/transfer-calls) |
| `context_details` | The recipient context the call ran with, including `recipient_phone_number` and any `recipient_data` variables you passed |
| `agent_context_details` | Agent-side context, when set |
| `batch_run_details` | Batch row detail for batch calls |
| `retry_count` | Attempts so far |
| `retry_config` | The [auto-retry](/docs/outbound/auto-retry) policy in force |
| `retry_history` | Prior attempts |
| `workflow_retries` | Retries driven by a [workflow](/docs/workflows/introduction) |

***

## Reading the payload well

<AccordionGroup>
  <Accordion title="Did a real conversation happen?">
    Check `conversation_duration > 0`, then look for a line starting with `user:` in `transcript`.
    `status` alone is not enough, because it comes from the telephony provider's lifecycle field and can
    read `completed` for a call the callee rejected. `telephony_data.hangup_reason` and
    `hangup_provider_code` tell you how it really ended.
  </Accordion>

  <Accordion title="Why did this call cost what it did?">
    Start at `cost_breakdown.llm_breakdown`, because extractions and summarization are separate LLM passes
    on top of the conversation, so an agent with many dispositions costs more per call. Then check
    `usage_breakdown.provider_source`: components billing to `bolna` are charged to your wallet, while
    ones billing to your own connected key are not.
  </Accordion>

  <Accordion title="Why was the agent slow?">
    `latency_data.time_to_first_audio` is what the caller felt. Split it with the per-component
    `time_to_connect` and `turns` values to see which leg was slow, then compare
    `usage_breakdown.endpointing` and `incremental_delay` against a call that felt fine. See
    [Call latencies](/docs/concepts/call-latencies).
  </Accordion>

  <Accordion title="Why did the agent talk over the caller?">
    `latency_data.interruption_stats`, specifically `agent_interrupted_user_count`,
    `longest_agent_monologue_ms` and `barge_in_recovery_rate`. The settings that change this behavior,
    interruption thresholds and endpointing, live in the [Engine tab](/docs/agent-setup/engine-tab).
  </Accordion>

  <Accordion title="Can I get this pushed to me instead of polling?">
    Yes. Set `webhook_url` on the agent and Bolna POSTs this same payload as the call progresses. Whitelist
    `13.203.39.153`, `13.126.9.249` and `13.202.133.53`. See
    [Webhooks](/docs/post-call/polling-call-status-webhooks).
  </Accordion>
</AccordionGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.