Structured outputs

Extract structured data from conversations using AI-powered analysis

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

1

Define your schema

Create a JSON Schema that describes the data you want to extract

2

Create structured output

Use the API to create a reusable structured output definition

4

Extract from calls

Data is automatically extracted after each call and stored in call artifacts

Quick start

Create a structured output

1import { VapiClient } from '@vapi-ai/server-sdk';
2
3const vapi = new VapiClient({ token: process.env.VAPI_API_KEY });
4
5const structuredOutput = await vapi.structuredOutputs.create({
6 name: "Customer Info",
7 type: "ai",
8 description: "Extract customer contact information",
9 schema: {
10 type: "object",
11 properties: {
12 firstName: {
13 type: "string",
14 description: "Customer's first name"
15 },
16 lastName: {
17 type: "string",
18 description: "Customer's last name"
19 },
20 email: {
21 type: "string",
22 format: "email",
23 description: "Customer's email address"
24 },
25 phone: {
26 type: "string",
27 pattern: "^\\+?[1-9]\\d{1,14}$",
28 description: "Phone number in E.164 format"
29 }
30 },
31 required: ["firstName", "lastName"]
32 }
33});
34
35console.log('Created structured output:', structuredOutput.id);

Add the structured output ID to your assistant’s configuration:

1const assistant = await vapi.assistants.create({
2 name: "Customer Support Agent",
3 // ... other assistant configuration
4 artifactPlan: {
5 structuredOutputIds: [structuredOutput.id]
6 }
7});

Access extracted data

After a call completes, retrieve the extracted data:

1const call = await vapi.calls.get(callId);
2
3// Access structured outputs from call artifacts
4const outputs = call.artifact?.structuredOutputs;
5
6if (outputs) {
7 for (const [outputId, data] of Object.entries(outputs)) {
8 console.log(`Output: ${data.name}`);
9 console.log(`Result:`, data.result);
10
11 // Handle the extracted data
12 if (data.result) {
13 // Process successful extraction
14 const { firstName, lastName, email, phone } = data.result;
15 // ... save to database, send notifications, etc.
16 }
17 }
18}

Schema types

Primitive types

Extract simple values directly:

1{
2 "type": "string",
3 "minLength": 1,
4 "maxLength": 100,
5 "pattern": "^[A-Z][a-z]+$"
6}

Object types

Extract structured data with multiple fields:

1{
2 "type": "object",
3 "properties": {
4 "name": {
5 "type": "string",
6 "description": "Full name"
7 },
8 "age": {
9 "type": "integer",
10 "minimum": 0,
11 "maximum": 120
12 },
13 "email": {
14 "type": "string",
15 "format": "email"
16 }
17 },
18 "required": ["name", "email"]
19}

Array types

Extract lists of items:

1{
2 "type": "array",
3 "items": {
4 "type": "object",
5 "properties": {
6 "product": {
7 "type": "string"
8 },
9 "quantity": {
10 "type": "integer",
11 "minimum": 1
12 }
13 }
14 },
15 "minItems": 1,
16 "maxItems": 10
17}

Nested structures

Extract complex hierarchical data:

1{
2 "type": "object",
3 "properties": {
4 "customer": {
5 "type": "object",
6 "properties": {
7 "name": {"type": "string"},
8 "contact": {
9 "type": "object",
10 "properties": {
11 "email": {"type": "string", "format": "email"},
12 "phone": {"type": "string"}
13 }
14 }
15 }
16 },
17 "order": {
18 "type": "object",
19 "properties": {
20 "items": {
21 "type": "array",
22 "items": {
23 "type": "object",
24 "properties": {
25 "sku": {"type": "string"},
26 "quantity": {"type": "integer"}
27 }
28 }
29 }
30 }
31 }
32 }
33}

Validation features

String formats

Vapi supports standard JSON Schema formats for validation:

FormatDescriptionExample
emailEmail addressesjohn@example.com
dateDate in YYYY-MM-DD2024-01-15
timeTime in HH:MM:SS14:30:00
date-timeISO 8601 datetime2024-01-15T14:30:00Z
uriValid URIhttps://example.com
uuidUUID format123e4567-e89b-12d3-a456-426614174000

Pattern matching

Use regular expressions for custom validation:

1{
2 "type": "string",
3 "pattern": "^[A-Z]{2}-\\d{6}$",
4 "description": "Order ID like US-123456"
5}

Conditional logic

Use if/then/else for conditional requirements:

1{
2 "type": "object",
3 "properties": {
4 "serviceType": {
5 "type": "string",
6 "enum": ["emergency", "scheduled"]
7 },
8 "appointmentTime": {
9 "type": "string",
10 "format": "date-time"
11 }
12 },
13 "if": {
14 "properties": {
15 "serviceType": {"const": "scheduled"}
16 }
17 },
18 "then": {
19 "required": ["appointmentTime"]
20 }
21}

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.

Conditions gate whether the output runs at all. This is different from the if/then/else schema 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

TypeFieldsOutput runs when
minMessagescount (integer ≥ 0)The conversation has at least count messages. count: 0 removes the runtime default minimum.
minCallDurationseconds (integer ≥ 0)The call lasted at least seconds seconds.
endedReasonoperator (oneOf or notOneOf), values (array of strings)The call’s 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:

1const structuredOutput = await vapi.structuredOutputs.create({
2 name: "Call Summary",
3 type: "ai",
4 description: "Summarize the conversation",
5 schema: {
6 type: "object",
7 properties: {
8 summary: { type: "string" }
9 }
10 },
11 conditions: [
12 { type: "minMessages", count: 4 },
13 { type: "minCallDuration", seconds: 10 },
14 { type: "endedReason", operator: "oneOf", values: ["customer-ended-call"] }
15 ]
16});

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:

1const structuredOutput = await vapi.structuredOutputs.create({
2 name: "Sentiment Analysis",
3 type: "ai",
4 schema: {
5 type: "object",
6 properties: {
7 sentiment: {
8 type: "string",
9 enum: ["positive", "negative", "neutral"]
10 },
11 confidence: {
12 type: "number",
13 minimum: 0,
14 maximum: 1
15 }
16 }
17 },
18 model: {
19 provider: "openai",
20 model: "gpt-4.1",
21 temperature: 0.1,
22 messages: [
23 {
24 role: "system",
25 content: "You are an expert at analyzing customer sentiment. Be precise and consistent."
26 },
27 {
28 role: "user",
29 content: "Extract {{structuredOutput.name}} using this schema:\n{{structuredOutput.schema}}\n\nAnalyze the sentiment of this conversation:\n{{transcript}}"
30 }
31 ]
32 }
33});

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

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

See Create structured output for every request field, including type, conditions, model, and assistantIds.

Update structured output

Updating the top-level schema type after creation requires the ?schemaOverride=true query parameter. See Update structured output.

List structured outputs

See List structured outputs for all query parameters, including filtering, sorting, and pagination.

Delete structured output

Common use cases

Customer information collection

1{
2 "name": "Customer Profile",
3 "type": "ai",
4 "schema": {
5 "type": "object",
6 "properties": {
7 "name": {"type": "string"},
8 "email": {"type": "string", "format": "email"},
9 "phone": {"type": "string"},
10 "accountNumber": {"type": "string"},
11 "preferredContactMethod": {
12 "type": "string",
13 "enum": ["email", "phone", "sms"]
14 }
15 }
16 }
17}

Appointment scheduling

1{
2 "name": "Appointment Request",
3 "type": "ai",
4 "schema": {
5 "type": "object",
6 "properties": {
7 "preferredDate": {"type": "string", "format": "date"},
8 "preferredTime": {"type": "string", "format": "time"},
9 "duration": {"type": "integer", "enum": [15, 30, 45, 60]},
10 "serviceType": {
11 "type": "string",
12 "enum": ["consultation", "follow-up", "procedure"]
13 },
14 "notes": {"type": "string"}
15 },
16 "required": ["preferredDate", "preferredTime", "serviceType"]
17 }
18}

Order processing

1{
2 "name": "Order Details",
3 "type": "ai",
4 "schema": {
5 "type": "object",
6 "properties": {
7 "items": {
8 "type": "array",
9 "items": {
10 "type": "object",
11 "properties": {
12 "product": {"type": "string"},
13 "quantity": {"type": "integer", "minimum": 1},
14 "specialInstructions": {"type": "string"}
15 },
16 "required": ["product", "quantity"]
17 }
18 },
19 "deliveryAddress": {
20 "type": "object",
21 "properties": {
22 "street": {"type": "string"},
23 "city": {"type": "string"},
24 "zipCode": {"type": "string", "pattern": "^\\d{5}$"}
25 }
26 },
27 "deliveryInstructions": {"type": "string"}
28 }
29 }
30}

Lead qualification

1{
2 "name": "Lead Information",
3 "type": "ai",
4 "schema": {
5 "type": "object",
6 "properties": {
7 "company": {"type": "string"},
8 "role": {"type": "string"},
9 "budget": {
10 "type": "string",
11 "enum": ["< $10k", "$10k-50k", "$50k-100k", "> $100k"]
12 },
13 "timeline": {
14 "type": "string",
15 "enum": ["immediate", "1-3 months", "3-6 months", "6+ months"]
16 },
17 "painPoints": {
18 "type": "array",
19 "items": {"type": "string"}
20 },
21 "nextSteps": {"type": "string"}
22 }
23 }
24}

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:

1if (data.result === null) {
2 console.log(`Extraction failed for ${data.name}`);
3 // Implement fallback logic
4}

Troubleshooting

No data extracted

1

Verify schema validity

Ensure your JSON Schema is valid and properly formatted

2

Check conversation content

Confirm the required information was actually mentioned

3

Review assistant configuration

Verify the structured output ID is linked to your assistant

4

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

  • Schema updates require ?schemaOverride=true parameter
  • Extraction occurs after call completion (not real-time)
  • Name field limited to 40 characters