Structured Outputs
Structured Outputs turn finished conversations into clean data. You decide which details matter — the caller's name, the service they asked for, the date they want, whether it was booked — and after every call, SMS conversation and email thread, Callable pulls those details out and returns them as validated JSON.
A dental clinic might extract this from every call:
{
"customer_name": "Sara Malik",
"phone": "555 010 0199",
"requested_service": "cleaning",
"appointment_date": "2026-11-17",
"booking_status": "booked",
"follow_up_required": null
}How it works
- Define an output on the Structured Outputs page (under Quality). Pick a starter template or start from scratch.
- Attach it to agents in each agent's Analysis tab, and switch extraction on. One output can be used by many agents.
- Publish the agent. The change goes through the normal draft and publish flow and shows in the agent's version history.
- Get results a few minutes after each conversation ends — on the conversation, in Extraction history, and in the
extraction.completedwebhook.
Extraction runs in the background after a conversation is over. It never runs during a live call and never slows the agent down.
Defining fields
Build the output with the visual editor — no JSON needed — or open the JSON Schema tab to edit the schema directly. Both stay in sync, and the schema is checked before it can be saved.
| Type | Use it for |
|---|---|
| Text | Names, reasons, free-form answers. Optional format: email, phone, date, date-time, time or URL. |
| Number / Whole number | Amounts, counts, party size. Optional minimum and maximum. |
| Yes / no | Flags such as "follow-up required". |
| Choice | A fixed list of options, such as booked / not_booked / callback_requested. |
| List | Several values, such as order numbers. Each item can be text, a number, a choice or a group. |
| Group | Related fields kept together, such as an address. Groups can nest up to four levels. |
Every field can be required or optional, has a description (the extractor reads it, so be specific — "the date the caller asked for, not today's date"), and can have rules (length, pattern, min/max) and a default.
Limits: up to 60 fields per output, and up to 20 outputs per agent.
Missing and uncertain information
Callable never invents a value to fill a field. Every result reports how each field was settled:
- Found — the conversation states it. The words that support it are shown with the value.
- Uncertain — something was said, but it needs a second look: a phone number spelled out in words, a date worked out from "next Thursday", or a weekday that doesn't match the date.
- Not determined — it wasn't said. The value is empty, and a required field is listed as missing.
- Default used — it wasn't said, and the field has a default, so the default was used and labelled as such.
- Rejected — a value broke the field's rules (for example, an email address in a phone field). It is removed and the reason is shown.
A result is complete when every required field was found and nothing was rejected.
What a customer says is recorded as their statement, not as a verified fact.
Testing before you go live
Every output has a Test extraction tab: paste a sample conversation (one line per turn, starting with Agent: or Customer:) and see exactly what would be extracted, field by field. The agent's Analysis tab has the same test for all of its attached outputs. A test is a real extraction, billed as usage, and nothing is saved. Dates like "next Tuesday" are worked out as if the conversation happened today.
When extraction runs
- Calls — shortly after the call ends.
- SMS — once the conversation has been quiet for 30 minutes.
- Email — once the thread has been quiet for 30 minutes.
Only conversations that start after extraction is switched on are extracted — turning it on never reprocesses old conversations. Each conversation is extracted once per output; you can re-run one from Extraction history.
Web chat widget conversations aren't stored on Callable's servers, so they can't be extracted automatically.
Where results appear
- On the call details page, the email thread in Logs, and the SMS conversation in a campaign's Messages, with a readable field view and the raw JSON.
- In Extraction history on the Structured Outputs page, filterable by agent, output and status.
- In the extraction.completed webhook, below.
The extraction.completed webhook
Turn on extraction.completed in the agent's webhook settings. After each extraction, Callable POSTs one event per output:
{
"event": "extraction.completed",
"sentAt": "2026-11-11T13:00:00.000Z",
"payload": {
"extractionId": "8f3c…",
"agentId": "agent_123",
"conversation": { "type": "call", "id": "call_456", "channel": "call", "at": "2026-11-11T12:51:46Z" },
"output": { "id": "1b51…", "name": "Appointment request", "version": 2 },
"result": { "customer_name": "Sara Malik", "appointment_date": null },
"fields": {
"customer_name": { "status": "found", "confidence": 1, "quote": "this is Sara Malik" },
"appointment_date": { "status": "missing", "confidence": null }
},
"missingFields": ["appointment_date"],
"validationErrors": [],
"complete": false
}
}conversation.type is call, sms_thread or email_thread.
Deliveries are signed with the agent's webhook secret in the x-callable-signature header: t=<unix seconds>,v1=<hex HMAC-SHA256 of "t.body">. Verify it before trusting the payload:
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const [t, v1] = header.split(",").map((p) => p.split("=")[1]);
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}Respond with a 2xx status. Failed deliveries are retried with increasing gaps, up to 5 attempts. Your endpoint's response body is never read.
Who sees what
- Admins and developers create and edit outputs, run tests and see extracted values.
- Members see which fields were found or missing, but never the values or the customer's quoted words.
- Only an admin can change how long results are kept.
- Workspaces whose data must stay in a specific region may not be able to use extraction yet.
Retention and billing
Results are kept for 90 days by default; an admin can choose anywhere from 7 to 730 days on the Retention tab. Results are stored apart from the transcript.
Each conversation's extraction is one model call that covers all of its outputs, billed from your balance like other usage, and charged once. If you've connected your own OpenAI key, extraction runs on it instead. Extraction uses the analysis model chosen on the agent's Analysis tab when it's an OpenAI or Anthropic model, and GPT-4o mini otherwise.