> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.vapi.ai/assistants/structured-outputs/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.vapi.ai/_mcp/server. # Structured outputs > Extract structured, schema-defined data from voice calls using AI-powered analysis. Covers field types, extraction timing, limitations, and HIPAA storage behavior. ## Overview Structured outputs enable automatic extraction of specific information from voice conversations in a structured format. Define your data requirements using JSON Schema, and we will identify and extract that information from your calls. **Key benefits:** * Extract customer information, appointments, and orders automatically * Validate data with JSON Schema constraints * Use any AI model for extraction (OpenAI, Anthropic, Google, Azure) * Reuse extraction definitions across multiple assistants ## How it works #### Define your schema Create a JSON Schema that describes the data you want to extract #### Create structured output Use the API to create a reusable structured output definition #### Link to assistants Connect the structured output to one or more assistants #### Extract from calls Data is automatically extracted after each call and stored in call artifacts ## Quick start ### Create a structured output **`TypeScript (Server SDK)`** ```typescript title="TypeScript (Server SDK)" import { VapiClient } from '@vapi-ai/server-sdk'; const vapi = new VapiClient({ token: process.env.VAPI_API_KEY }); const structuredOutput = await vapi.structuredOutputs.create({ name: "Customer Info", type: "ai", description: "Extract customer contact information", schema: { type: "object", properties: { firstName: { type: "string", description: "Customer's first name" }, lastName: { type: "string", description: "Customer's last name" }, email: { type: "string", format: "email", description: "Customer's email address" }, phone: { type: "string", pattern: "^\\+?[1-9]\\d{1,14}$", description: "Phone number in E.164 format" } }, required: ["firstName", "lastName"] } }); console.log('Created structured output:', structuredOutput.id); ``` **`Python (Server SDK)`** ```python title="Python (Server SDK)" import os from vapi import Vapi vapi = Vapi(token=os.environ['VAPI_API_KEY']) structured_output = vapi.structured_outputs.create( name="Customer Info", type="ai", description="Extract customer contact information", schema={ "type": "object", "properties": { "firstName": { "type": "string", "description": "Customer's first name" }, "lastName": { "type": "string", "description": "Customer's last name" }, "email": { "type": "string", "format": "email", "description": "Customer's email address" }, "phone": { "type": "string", "pattern": "^\\+?[1-9]\\d{1,14}$", "description": "Phone number in E.164 format" } }, "required": ["firstName", "lastName"] } ) print(f"Created structured output: {structured_output.id}") ``` **`cURL`** ```bash title="cURL" curl -X POST https://api.vapi.ai/structured-output \ -H "Authorization: Bearer $VAPI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Customer Info", "type": "ai", "description": "Extract customer contact information", "schema": { "type": "object", "properties": { "firstName": { "type": "string", "description": "Customer'\''s first name" }, "lastName": { "type": "string", "description": "Customer'\''s last name" }, "email": { "type": "string", "format": "email", "description": "Customer'\''s email address" }, "phone": { "type": "string", "pattern": "^\\+?[1-9]\\d{1,14}$", "description": "Phone number in E.164 format" } }, "required": ["firstName", "lastName"] } }' ``` ### Link to an assistant Add the structured output ID to your assistant's configuration: **`TypeScript (Server SDK)`** ```typescript title="TypeScript (Server SDK)" const assistant = await vapi.assistants.create({ name: "Customer Support Agent", // ... other assistant configuration artifactPlan: { structuredOutputIds: [structuredOutput.id] } }); ``` **`Python (Server SDK)`** ```python title="Python (Server SDK)" assistant = vapi.assistants.create( name="Customer Support Agent", # ... other assistant configuration artifact_plan={ "structuredOutputIds": [structured_output.id] } ) ``` **`cURL`** ```bash title="cURL" curl -X POST https://api.vapi.ai/assistant \ -H "Authorization: Bearer $VAPI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Customer Support Agent", "artifactPlan": { "structuredOutputIds": ["output-id-here"] } }' ``` ### Access extracted data After a call completes, retrieve the extracted data: **`TypeScript (Server SDK)`** ```typescript title="TypeScript (Server SDK)" const call = await vapi.calls.get(callId); // Access structured outputs from call artifacts const outputs = call.artifact?.structuredOutputs; if (outputs) { for (const [outputId, data] of Object.entries(outputs)) { console.log(`Output: ${data.name}`); console.log(`Result:`, data.result); // Handle the extracted data if (data.result) { // Process successful extraction const { firstName, lastName, email, phone } = data.result; // ... save to database, send notifications, etc. } } } ``` **`Python (Server SDK)`** ```python title="Python (Server SDK)" call = vapi.calls.get(call_id) # Access structured outputs from call artifacts outputs = call.artifact.get('structuredOutputs', {}) for output_id, data in outputs.items(): print(f"Output: {data['name']}") print(f"Result: {data['result']}") # Handle the extracted data if data['result']: # Process successful extraction result = data['result'] first_name = result.get('firstName') last_name = result.get('lastName') email = result.get('email') phone = result.get('phone') # ... save to database, send notifications, etc. ``` **`Webhook Response`** ```javascript title="Webhook Response" // In your webhook handler app.post('/vapi/webhook', (req, res) => { const { message } = req.body; if (message.type === 'end-of-call-report') { const outputs = message.artifact?.structuredOutputs; if (outputs) { Object.entries(outputs).forEach(([outputId, data]) => { console.log(`Extracted ${data.name}:`, data.result); // Process the extracted data }); } } res.status(200).send('OK'); }); ``` ## Schema types ### Primitive types Extract simple values directly: **`String`** ```json title="String" { "type": "string", "minLength": 1, "maxLength": 100, "pattern": "^[A-Z][a-z]+$" } ``` **`Number`** ```json title="Number" { "type": "number", "minimum": 0, "maximum": 100, "multipleOf": 0.5 } ``` **`Boolean`** ```json title="Boolean" { "type": "boolean", "description": "Whether customer agreed to terms" } ``` **`Enum`** ```json title="Enum" { "type": "string", "enum": ["small", "medium", "large", "extra-large"] } ``` ### Object types Extract structured data with multiple fields: ```json { "type": "object", "properties": { "name": { "type": "string", "description": "Full name" }, "age": { "type": "integer", "minimum": 0, "maximum": 120 }, "email": { "type": "string", "format": "email" } }, "required": ["name", "email"] } ``` ### Array types Extract lists of items: ```json { "type": "array", "items": { "type": "object", "properties": { "product": { "type": "string" }, "quantity": { "type": "integer", "minimum": 1 } } }, "minItems": 1, "maxItems": 10 } ``` ### Nested structures Extract complex hierarchical data: ```json { "type": "object", "properties": { "customer": { "type": "object", "properties": { "name": {"type": "string"}, "contact": { "type": "object", "properties": { "email": {"type": "string", "format": "email"}, "phone": {"type": "string"} } } } }, "order": { "type": "object", "properties": { "items": { "type": "array", "items": { "type": "object", "properties": { "sku": {"type": "string"}, "quantity": {"type": "integer"} } } } } } } } ``` ## Validation features ### String formats Vapi supports standard JSON Schema formats for validation: | Format | Description | Example | | ----------- | ------------------ | ------------------------------------------- | | `email` | Email addresses | [john@example.com](mailto:john@example.com) | | `date` | Date in YYYY-MM-DD | 2024-01-15 | | `time` | Time in HH:MM:SS | 14:30:00 | | `date-time` | ISO 8601 datetime | 2024-01-15T14:30:00Z | | `uri` | Valid URI | [https://example.com](https://example.com) | | `uuid` | UUID format | 123e4567-e89b-12d3-a456-426614174000 | ### Pattern matching Use regular expressions for custom validation: ```json { "type": "string", "pattern": "^[A-Z]{2}-\\d{6}$", "description": "Order ID like US-123456" } ``` ### Conditional logic Use `if/then/else` for conditional requirements: ```json { "type": "object", "properties": { "serviceType": { "type": "string", "enum": ["emergency", "scheduled"] }, "appointmentTime": { "type": "string", "format": "date-time" } }, "if": { "properties": { "serviceType": {"const": "scheduled"} } }, "then": { "required": ["appointmentTime"] } } ``` ## Conditional generation By default, every linked structured output runs after each call. Attach **conditions** to a structured output so it only generates when the call meets your criteria — for example, skip extraction on calls that barely started, or only run an output when the call ended a certain way. > **Note** > > Conditions gate **whether the output runs at all**. This is different from the [`if/then/else` schema logic](#conditional-logic) above, which shapes the data *within* a single extraction. ### How conditions work * Add a `conditions` array to a structured output. * **Every condition must pass** for the output to run (AND semantics). * When `conditions` is omitted or empty, no user-defined conditions gate the output (runtime defaults still apply). * On update (`PATCH`), send `conditions: null` to clear a previously saved gate. When a condition isn't met, the output is **skipped** rather than failed. Skipped outputs are surfaced in the **assistant preview**, **call logs**, and **sessions**, so you can see which outputs ran and which were gated out. ### Condition types | Type | Fields | Output runs when | | ----------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `minMessages` | `count` (integer ≥ 0) | The conversation has at least `count` messages. `count: 0` removes the runtime default minimum. | | `minCallDuration` | `seconds` (integer ≥ 0) | The call lasted at least `seconds` seconds. | | `endedReason` | `operator` (`oneOf` or `notOneOf`), `values` (array of strings) | The call's [ended reason](/calls/call-ended-reason) passes the membership test against `values`. `oneOf` runs the output only if the ended reason is in `values`; `notOneOf` runs it only if the ended reason is not in `values`. | ### Example Only extract a call summary when the call had a real conversation (at least 4 messages and 10 seconds) and the customer ended it: **`TypeScript (Server SDK)`** ```typescript title="TypeScript (Server SDK)" const structuredOutput = await vapi.structuredOutputs.create({ name: "Call Summary", type: "ai", description: "Summarize the conversation", schema: { type: "object", properties: { summary: { type: "string" } } }, conditions: [ { type: "minMessages", count: 4 }, { type: "minCallDuration", seconds: 10 }, { type: "endedReason", operator: "oneOf", values: ["customer-ended-call"] } ] }); ``` **`Python (Server SDK)`** ```python title="Python (Server SDK)" structured_output = vapi.structured_outputs.create( name="Call Summary", type="ai", description="Summarize the conversation", schema={ "type": "object", "properties": { "summary": {"type": "string"} } }, conditions=[ {"type": "minMessages", "count": 4}, {"type": "minCallDuration", "seconds": 10}, {"type": "endedReason", "operator": "oneOf", "values": ["customer-ended-call"]} ] ) ``` **`cURL`** ```bash title="cURL" curl -X POST https://api.vapi.ai/structured-output \ -H "Authorization: Bearer $VAPI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Call Summary", "type": "ai", "description": "Summarize the conversation", "schema": { "type": "object", "properties": { "summary": { "type": "string" } } }, "conditions": [ { "type": "minMessages", "count": 4 }, { "type": "minCallDuration", "seconds": 10 }, { "type": "endedReason", "operator": "oneOf", "values": ["customer-ended-call"] } ] }' ``` ## Custom models By default, structured outputs are extracted with GPT-4.1. Configure the `model` to use a different provider or model, or to supply your own extraction prompts: **`TypeScript`** ```typescript title="TypeScript" const structuredOutput = await vapi.structuredOutputs.create({ name: "Sentiment Analysis", type: "ai", schema: { type: "object", properties: { sentiment: { type: "string", enum: ["positive", "negative", "neutral"] }, confidence: { type: "number", minimum: 0, maximum: 1 } } }, model: { provider: "openai", model: "gpt-4.1", temperature: 0.1, messages: [ { role: "system", content: "You are an expert at analyzing customer sentiment. Be precise and consistent." }, { role: "user", content: "Extract {{structuredOutput.name}} using this schema:\n{{structuredOutput.schema}}\n\nAnalyze the sentiment of this conversation:\n{{transcript}}" } ] } }); ``` **`Python`** ```python title="Python" structured_output = vapi.structured_outputs.create( name="Sentiment Analysis", type="ai", schema={ "type": "object", "properties": { "sentiment": { "type": "string", "enum": ["positive", "negative", "neutral"] }, "confidence": { "type": "number", "minimum": 0, "maximum": 1 } } }, model={ "provider": "openai", "model": "gpt-4.1", "temperature": 0.1, "messages": [ { "role": "system", "content": "You are an expert at analyzing customer sentiment. Be precise and consistent." }, { "role": "user", "content": "Extract {{structuredOutput.name}} using this schema:\n{{structuredOutput.schema}}\n\nAnalyze the sentiment of this conversation:\n{{transcript}}" } ] } ) ``` ### Available variables Use these variables in custom prompts: * `{{transcript}}` - Full conversation transcript * `{{messages}}` - Conversation messages array (JSON) * `{{endedReason}}` - How the call ended * `{{duration}}` - Call duration in seconds * `{{startedAt}}` - Call start time (ISO 8601) * `{{endedAt}}` - Call end time (ISO 8601) * `{{systemPrompt}}` - The assistant's system prompt * `{{structuredOutput}}` - The full structured output definition * `{{structuredOutput.name}}` - Output name * `{{structuredOutput.description}}` - Output description * `{{structuredOutput.schema}}` - Schema definition > **Note** > > When you supply custom `messages`, reference either `{{transcript}}` or `{{messages}}` for the conversation, and a variation of `{{structuredOutput}}` so the model has the schema definition. ## API reference The full set of request fields, response types, and query parameters lives in the API reference. Refer there for all possible values rather than duplicating them here. ### Create structured output ### Request POST [https://api.vapi.ai/structured-output](https://api.vapi.ai/structured-output) ```curl curl -X POST https://api.vapi.ai/structured-output \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "string", "schema": { "type": "string" } }' ``` ```python from vapi import Vapi, JsonSchema client = Vapi( token="YOUR_TOKEN_HERE", ) client.structured_outputs.structured_output_controller_create( name="string", schema=JsonSchema( type="string", ), ) ``` ```go package example import ( context "context" serversdkgo "github.com/VapiAI/server-sdk-go" client "github.com/VapiAI/server-sdk-go/client" option "github.com/VapiAI/server-sdk-go/option" ) func do() { client := client.NewClient( option.WithToken( "YOUR_TOKEN_HERE", ), ) request := &serversdkgo.CreateStructuredOutputDto{ Name: "string", Schema: &serversdkgo.JsonSchema{ Type: serversdkgo.JsonSchemaTypeString, }, } client.StructuredOutputs.StructuredOutputControllerCreate( context.TODO(), request, ) } ``` See [Create structured output](/api-reference/structured-outputs/structured-output-controller-create) for every request field, including `type`, `conditions`, `model`, and `assistantIds`. ### Update structured output ### Request PATCH [https://api.vapi.ai/structured-output/\{id}](https://api.vapi.ai/structured-output/\{id}) ```curl curl -X PATCH "https://api.vapi.ai/structured-output/id?schemaOverride=schemaOverride" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{}' ``` ```python from vapi import Vapi client = Vapi( token="YOUR_TOKEN_HERE", ) client.structured_outputs.structured_output_controller_update( id="id", schema_override="schemaOverride", ) ``` ```go package example import ( context "context" serversdkgo "github.com/VapiAI/server-sdk-go" client "github.com/VapiAI/server-sdk-go/client" option "github.com/VapiAI/server-sdk-go/option" ) func do() { client := client.NewClient( option.WithToken( "YOUR_TOKEN_HERE", ), ) request := &serversdkgo.UpdateStructuredOutputDto{ Id: "id", SchemaOverride: "schemaOverride", } client.StructuredOutputs.StructuredOutputControllerUpdate( context.TODO(), request, ) } ``` > **Note** > > Updating the top-level schema type after creation requires the `?schemaOverride=true` query parameter. See [Update structured output](/api-reference/structured-outputs/structured-output-controller-update). ### List structured outputs ### Request GET [https://api.vapi.ai/structured-output](https://api.vapi.ai/structured-output) ```curl curl https://api.vapi.ai/structured-output \ -H "Authorization: Bearer " ``` ```python from vapi import Vapi client = Vapi( token="YOUR_TOKEN_HERE", ) client.structured_outputs.structured_output_controller_find_all() ``` ```go package example import ( context "context" serversdkgo "github.com/VapiAI/server-sdk-go" client "github.com/VapiAI/server-sdk-go/client" option "github.com/VapiAI/server-sdk-go/option" ) func do() { client := client.NewClient( option.WithToken( "YOUR_TOKEN_HERE", ), ) request := &serversdkgo.StructuredOutputControllerFindAllRequest{} client.StructuredOutputs.StructuredOutputControllerFindAll( context.TODO(), request, ) } ``` See [List structured outputs](/api-reference/structured-outputs/structured-output-controller-find-all) for all query parameters, including filtering, sorting, and pagination. ### Delete structured output ### Request DELETE [https://api.vapi.ai/structured-output/\{id}](https://api.vapi.ai/structured-output/\{id}) ```curl curl -X DELETE https://api.vapi.ai/structured-output/id \ -H "Authorization: Bearer " ``` ```python from vapi import Vapi client = Vapi( token="YOUR_TOKEN_HERE", ) client.structured_outputs.structured_output_controller_remove( id="id", ) ``` ```go package example import ( context "context" serversdkgo "github.com/VapiAI/server-sdk-go" client "github.com/VapiAI/server-sdk-go/client" option "github.com/VapiAI/server-sdk-go/option" ) func do() { client := client.NewClient( option.WithToken( "YOUR_TOKEN_HERE", ), ) request := &serversdkgo.StructuredOutputControllerRemoveRequest{ Id: "id", } client.StructuredOutputs.StructuredOutputControllerRemove( context.TODO(), request, ) } ``` ## Common use cases ### Customer information collection ```json { "name": "Customer Profile", "type": "ai", "schema": { "type": "object", "properties": { "name": {"type": "string"}, "email": {"type": "string", "format": "email"}, "phone": {"type": "string"}, "accountNumber": {"type": "string"}, "preferredContactMethod": { "type": "string", "enum": ["email", "phone", "sms"] } } } } ``` ### Appointment scheduling ```json { "name": "Appointment Request", "type": "ai", "schema": { "type": "object", "properties": { "preferredDate": {"type": "string", "format": "date"}, "preferredTime": {"type": "string", "format": "time"}, "duration": {"type": "integer", "enum": [15, 30, 45, 60]}, "serviceType": { "type": "string", "enum": ["consultation", "follow-up", "procedure"] }, "notes": {"type": "string"} }, "required": ["preferredDate", "preferredTime", "serviceType"] } } ``` ### Order processing ```json { "name": "Order Details", "type": "ai", "schema": { "type": "object", "properties": { "items": { "type": "array", "items": { "type": "object", "properties": { "product": {"type": "string"}, "quantity": {"type": "integer", "minimum": 1}, "specialInstructions": {"type": "string"} }, "required": ["product", "quantity"] } }, "deliveryAddress": { "type": "object", "properties": { "street": {"type": "string"}, "city": {"type": "string"}, "zipCode": {"type": "string", "pattern": "^\\d{5}$"} } }, "deliveryInstructions": {"type": "string"} } } } ``` ### Lead qualification ```json { "name": "Lead Information", "type": "ai", "schema": { "type": "object", "properties": { "company": {"type": "string"}, "role": {"type": "string"}, "budget": { "type": "string", "enum": ["< $10k", "$10k-50k", "$50k-100k", "> $100k"] }, "timeline": { "type": "string", "enum": ["immediate", "1-3 months", "3-6 months", "6+ months"] }, "painPoints": { "type": "array", "items": {"type": "string"} }, "nextSteps": {"type": "string"} } } } ``` ## Best practices #### Start simple Begin with basic schemas and add complexity as needed. Test with real conversations before adding advanced features. #### Use descriptive names Help the AI understand what to extract by using clear field names and descriptions in your schema. #### Set appropriate constraints Balance flexibility with validation. Too strict and extraction may fail; too loose and data quality suffers. #### Handle optional fields Only mark fields as required if they're truly essential. Use optional fields for information that might not be mentioned. ### Performance tips * **Keep schemas focused**: Extract only what you need to minimize processing time * **Use appropriate models**: use a capable model (for example, GPT-4.1) for complex schemas; lighter models can handle simpler ones * **Set low temperature**: Use 0.1 or lower for consistent extraction * **Monitor success rates**: Track extraction failures and adjust schemas accordingly ### Error handling Always check for null results which indicate extraction failure: ```typescript if (data.result === null) { console.log(`Extraction failed for ${data.name}`); // Implement fallback logic } ``` ## Troubleshooting ### No data extracted #### Verify schema validity Ensure your JSON Schema is valid and properly formatted #### Check conversation content Confirm the required information was actually mentioned #### Review assistant configuration Verify the structured output ID is linked to your assistant #### Test with simpler schema Try a basic schema to isolate the issue ### Incorrect extraction * Add more descriptive field descriptions * Provide examples in custom prompts * Use stricter validation patterns * Lower the model temperature ### Partial extraction * Make fields optional if they might not be mentioned * Verify data types match expected values ## Limitations > **Warning** > > * Schema updates require `?schemaOverride=true` parameter > * Extraction occurs after call completion (not real-time) > * Name field limited to 40 characters ## Related * [create-structured-output skill](/agent-skills#create-structured-output) - Use an AI coding assistant to define, attach, and verify reusable post-call extraction. * [Call analysis](/assistants/call-analysis) - Summarize and evaluate calls * [Function tools](/tools/custom-tools) - Trigger actions during calls * [Webhooks](/server-url) - Receive extracted data via webhooks * [Variables](/assistants/dynamic-variables) - Use dynamic data in conversations > Extract structured data from conversations using AI-powered analysis ## Docs - [Structured outputs quickstart](https://docs.vapi.ai/assistants/structured-outputs-quickstart.md): Set up structured data extraction from calls in a few minutes. Create a schema, link it to an assistant, test extraction, and configure HIPAA-safe storage settings. - [Structured outputs examples](https://docs.vapi.ai/assistants/structured-outputs-examples.md): Production-ready structured outputs examples for Vapi, with complete schemas, configuration, and integration code for common business scenarios.