Skip to main content
Legacy post-call extraction has been removed. custom_extractions and agent_extraction return null on new calls, and flat keys no longer appear in extracted_data. All results live in extracted_data, nested category → extraction name → result object, and each value is an object rather than a scalar. Any webhook, API, or CSV consumer still reading the old shape reads nothing.
Completed calls keep the results and shape they were stored with. This guide covers what changed and how to rebuild each legacy feature as an extraction.

Am I affected?

Check a webhook payload or GET /executions/{execution_id} response from before the change:
Already migrated? Nested keys mean you are on Extractions and no action is needed. The legacy toggles are gone from the agent Extractions tab, which now configures webhooks and Extractions only.

Migration by feature

Four features, four different paths. Open the one whose field your integration reads.

Extraction

extracted_data · flat keys → Rebuild as extractions. One extraction per field in your prose box. Keys stay stable if you reuse your current names.

Custom analytics

custom_extractions → Rebuild, one per item. Types map onto answer types. Numbers become strings, and min/max clamping has no equivalent — use validation.is_valid instead.

Agent extraction

agent_extraction → Discontinued. No replacement template and no migration on your behalf. Identify the fields from a payload and author your own.

Call summary

summary → No change required. Stays a flat top-level key, now sourced from the Call Summary extraction. Keep reading it as you do today.

What the payload looks like

Category is a grouping label you choose. Name is the key you choose.

Result object fields

Which field to read:

Breaking changes

  • Values are two levels deeper. extracted_data.call_reason becomes extracted_data["Call Details"]["Call Reason"].
  • Values are objects, not scalars. Select objective or subjective from the result.
  • subjective and objective are always JSON strings. A numeric extraction returns "250000", not 250000. Custom Analytics returned a real number, so parse it yourself. Min/max clamping has no equivalent; use validation.is_valid to confirm the value parses.
  • custom_extractions and agent_extraction return null. The keys stay in the payload, so a consumer reading them finds them present and empty rather than missing.
  • summary stays a flat top-level key. Webhook payloads keep summary as a string at the top level, populated by the Call Summary extraction instead of the legacy summarization task. If you read top-level summary, no change is required.
  • objective cannot drift. Pre-defined answers are constrained by the request, so the value matches your configured option character for character. Switch fuzzy matching to exact comparison.
  • New capabilities: per-answer confidence, LLM reasoning, and format validation. Route confidence_label: "Low" to human review.

Expected formats

A failed check never loses the value. is_valid: false is set and subjective holds the LLM’s response unchanged.

Calls with no user speech

Enabled per account rather than by default. Email support@bolna.ai to turn it on; it then applies to every agent on the account. When a call contains no user turns (voicemail, an immediate hangup, dead air), the LLM has nothing to work from and can still invent a plausible answer. With this enabled, every extraction on the agent returns a fixed sentinel instead.
No user turns detected
No User Turn Detected appears in subjective, objective, reasoning_subjective and reasoning_objective, in every extraction across every category.
  • The top-level summary key returns it too. Anything writing summary into a CRM note, ticket field, or report writes that string. Check for it before persisting.
  • objective returns the sentinel even though it is not one of your configured options. Exact-match comparisons hit no branch, so handle the sentinel before comparing.
  • confidence is 1.0 and confidence_label "High". The score reflects certainty that no user spoke, not certainty about an answer. Filtering on confidence alone does not exclude these calls.
  • Typed extractions return the sentinel as a string. A numeric extraction returns "No User Turn Detected", so guard your parsing.
Check for the sentinel before reading any extraction value, and route these calls to your no-contact path rather than treating them as answered.

CSV exports

Seven columns per extraction instead of one, following extracted_data_<Category>_<Name>_<field>. Update any loader or formula referencing columns by name.

Configuring through the API

Extractions are managed under /dispositions. “Disposition” is the API’s name for a single extraction. The /extractions path is an internal template registry, not this endpoint.

Creating one

POST
  • name is the result key. category is the key above it.
  • question is the LLM prompt. State what to look at, what to ignore, and what each option means.
  • Always include a fallback option such as NA so the model is never forced to pick a real answer for a question the call never reached.
  • Set is_subjective: true for free text, is_objective: true for pre-defined, or both.
  • subjective_type accepts text, numeric, boolean, email, timestamp, regex. regex also requires subjective_type_config: {"pattern": "…"}.
  • Extractions in one category are answered in a single LLM call, so grouping related fields keeps their answers consistent with each other.

Verifying a migration

POST /v2/agent/{agent_id}/dispositions/test
Returns {"extracted_data": {…}} in exactly the shape a live call produces. Paste a real past transcript and diff against what your old configuration returned for that call. No call is spent.

Reading historical calls

extracted_data was a shared field, so calls placed while both a legacy extraction task and Extractions were enabled hold both shapes at once — flat legacy keys alongside nested categories:
A call from the transition period
Check which shape you have rather than assuming. A nested category is an object whose own values are objects containing subjective and objective. Code that branches on the nested shape reads both new calls and historical ones correctly.

FAQ

No. Completed calls keep the results and shape they were stored with, readable indefinitely. Only calls placed after you migrate use the new shape. Code that reads historical calls must keep handling the old shape permanently, not just until the cutoff.
No. They are removed, and an agent still carrying a legacy configuration simply produces no results from it. Rebuild the fields you need as extractions.
Free-text answers may be phrased differently for an identical call, because the prompt structure differs. Pre-defined answers are stable, since they can only be one of your configured values. Diff side by side before disabling anything.
Yes. Set each extraction’s name to your current key. The name is unchanged; the path is one level deeper.
No. Recreating extractions is self-serve, through the dashboard or the /dispositions API. This includes Agent Extraction, which is discontinued rather than replaced.
Agents keep taking calls. Only structured results stop: legacy fields return null, and no extractions run unless configured. Transcripts, recordings and call summaries are unaffected.
Email support@bolna.ai with the execution_id of the call, the field you were reading, and the value you expected. The execution_id is the id in the webhook payload and in GET /executions responses. One id is much quicker for us to work with than a description of the problem — we can pull that exact execution and see which extraction path produced the value.
Email support@bolna.ai with an execution_id for a payload that does not match this guide.

Next steps

Using extractions

Configure extractions in the dashboard

Dispositions API

Manage extractions programmatically

Webhooks

Receive extraction data in real-time