You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/learnings/README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# Gotchas & Best Practices
2
2
3
-
Non-obvious behaviors, silent defaults, and foot-guns in the Vapi backend that affect how your YAML/JSON resources behave at runtime. This is a companion to the API reference — it covers what the docs _don't_ tell you.
3
+
Non-obvious behaviors, silent defaults, and foot-guns in the Vapi platform that affect how your YAML/JSON resources behave at runtime. This is a companion to the API reference — it covers what the docs _don't_ tell you.
4
4
5
5
Each file in this directory covers a specific resource type so you can load only the context you need:
Copy file name to clipboardExpand all lines: docs/learnings/assistants.md
+9-9Lines changed: 9 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,7 +8,7 @@ Non-obvious behaviors and silent defaults for Vapi assistant settings.
8
8
9
9
### `temperature` defaults to 0, `maxTokens` defaults to 250
10
10
11
-
If omitted, the backend fills`temperature: 0` and `maxTokens: 250`. This can cause unexpectedly short responses.
11
+
If omitted, Vapi defaults to`temperature: 0` and `maxTokens: 250`. This can cause unexpectedly short responses.
12
12
13
13
**Recommendation:** Set `maxTokens` explicitly (e.g., 1000–4000) for assistants that need longer responses.
14
14
@@ -22,7 +22,7 @@ If omitted, the backend fills `temperature: 0` and `maxTokens: 250`. This can ca
22
22
23
23
### `emotionRecognitionEnabled` has no runtime effect
24
24
25
-
Despite being a configurable field, the endpointing buffer has an explicit no-op branch for this flag. Don't rely on it for behavior changes.
25
+
Despite being a configurable field, this setting currently has **no effect** on call behavior. Don't rely on it.
26
26
27
27
---
28
28
@@ -50,7 +50,7 @@ If omitted, transcripts with confidence below 0.4 may be **ignored** entirely (n
50
50
51
51
### `smartEndpointingPlan`**owns** turn detection when set
52
52
53
-
If you configure `startSpeakingPlan.smartEndpointingPlan`, the transcriber's own `endpointing` settings (VAD timeouts, etc.) are **not used** for turn detection. Smart endpointing takes full control.
53
+
If you configure `startSpeakingPlan.smartEndpointingPlan`, the transcriber's own `endpointing` settings (voice activity detection timeouts, etc.) are **not used** for turn detection. Smart endpointing takes full control.
54
54
55
55
**Recommendation:** Don't set both and expect them to combine. Choose one approach.
56
56
@@ -76,7 +76,7 @@ Hooks that fail validation are dropped without error. Your call will proceed wit
76
76
77
77
### Missing hook `toolId` is a warning, not an error
78
78
79
-
If a hook references a `toolId` that doesn't exist, the backend logs a warning and continues. This is different from `model.toolIds` where a missing ID **errors the call**.
79
+
If a hook references a `toolId` that doesn't exist, Vapi logs a warning and continues. This is different from `model.toolIds` where a missing ID **errors the call**.
80
80
81
81
### Hook events are independent from timeout settings
82
82
@@ -123,8 +123,8 @@ If not set, there's a 0.4-second pause after the user stops speaking before the
123
123
124
124
### `waitSeconds` and `smartEndpointingPlan` control different things
125
125
126
-
-`waitSeconds`drives the **TurnTakingBuffer**: it corks assistant audio output for that duration after VAD detects speech.
127
-
-`smartEndpointingPlan`drives the **EndpointingBuffer**: it determines when the user has finished their turn using an AI model or heuristic.
126
+
-`waitSeconds`controls a short pause on assistant audio after voice activity is detected — it delays the assistant's response by that duration.
127
+
-`smartEndpointingPlan`controls how end-of-turn is detected — it determines when the user has finished speaking using an AI model or heuristic.
128
128
129
129
These are complementary, not alternatives.
130
130
@@ -152,7 +152,7 @@ Unless you explicitly set `analysisPlan.summaryPlan.enabled: false`, post-call s
152
152
153
153
### Recording is enabled by default
154
154
155
-
Unless you set `artifactPlan.recordingEnabled: false`, calls are recorded. The backend falls back to `assistant.recordingEnabled` (deprecated), then defaults to `true`.
155
+
Unless you set `artifactPlan.recordingEnabled: false`, calls are recorded. Vapi also checks `assistant.recordingEnabled` (deprecated) before defaulting to `true`.
156
156
157
157
### Default transcript labels are "AI" and "User"
158
158
@@ -198,7 +198,7 @@ Omitting `serverMessages` does **not** mean "no webhooks." It means "all default
198
198
199
199
## HIPAA / Compliance
200
200
201
-
When HIPAA is enabled (on org or assistant) **and** the `ENABLE_ASSISTANT_PROVIDER_HIPAA_VALIDATION` feature flag is active, the model, voice, and transcriber providers must be on an approved allowlist. Non-compliant providers will **fail validation** on create/update.
201
+
When HIPAA is enabled (on org or assistant) and HIPAA provider validation is enforced for your org, the model, voice, and transcriber providers must be on Vapi's approved allowlist. Non-compliant providers will **fail validation** on create/update.
202
202
203
203
---
204
204
@@ -209,7 +209,7 @@ When HIPAA is enabled (on org or assistant) **and** the `ENABLE_ASSISTANT_PROVID
209
209
1. Inline `model.tools` are processed first
210
210
2. Each `toolId` is resolved from the org's saved tools — **a missing ID errors the call**
211
211
3. Results are merged: `[...inline tools, ...resolved toolIds]`
212
-
4. MCP tools are expanded from parent tool metadata
212
+
4. MCP tools are expanded from configured integrations
Copy file name to clipboardExpand all lines: docs/learnings/simulations.md
+4-4Lines changed: 4 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -39,14 +39,14 @@ Simulation evaluations support these comparators:
39
39
40
40
Evaluations that require audio (`target: messages-with-audio`) are **skipped** in chat-mode simulations with `passed: false` and `isSkipped: true`.
41
41
42
-
**Critical:** If **all** required evaluations are audio-dependent and the simulation runs in chat mode, they're all skipped — and `evaluationPlanPassedCheck` returns **`true`** (0 required results = pass). This means your test suite can silently pass with no actual evaluation.
42
+
**Critical:** If **all** required evaluations are audio-dependent and the simulation runs in chat mode, they're all skipped — and the run is treated as **passed** (0 required results = pass). This means your test suite can silently pass with no actual evaluation.
43
43
44
44
**Recommendation:** Include at least one text-based (`target: messages`) evaluation in every simulation that runs in chat mode.
45
45
46
46
---
47
47
48
48
## Missing References
49
49
50
-
-**At run creation (API):** Missing scenario or personality IDs throw `BadRequestException`immediately.
Copy file name to clipboardExpand all lines: docs/learnings/squads.md
+8-10Lines changed: 8 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -40,7 +40,7 @@ assistantOverrides:
40
40
41
41
## assistantDestinations vs Inline Tools
42
42
43
-
`member.assistantDestinations` is **sugar** that the backend converts into real tools:
43
+
`member.assistantDestinations` is **shorthand** that Vapi converts into real tools:
44
44
45
45
- Destinations with `assistantId`, `contextEngineeringPlan`, or `variableExtractionPlan` → **handoff tools**
46
46
- All other destinations → merged into a **transferCall tool** (creating one if needed)
@@ -51,20 +51,18 @@ Both mechanisms can coexist on the same member. If you use both `assistantDestin
51
51
52
52
## Context During Handoffs
53
53
54
-
After a handoff, the new assistant does NOT get a raw copy of all prior messages. The backend applies `squadTransferMessagesApply` which may summarize, filter, or restructure the conversation history. If `handoffMessages` are provided on the destination, they **replace** the active message context entirely.
54
+
After a handoff, the new assistant does NOT get a raw copy of all prior messages. Vapi may summarize, filter, or restructure the conversation history during the transfer. If `handoffMessages` are provided on the destination, they **replace** the active message context entirely.
55
55
56
56
---
57
57
58
58
## Override Merge Order
59
59
60
-
```
61
-
Final = deepMerge(
62
-
assistant,
63
-
member.assistantOverrides,
64
-
squad.membersOverrides,
65
-
call.squadOverrides
66
-
)
67
-
```
60
+
The final assistant configuration is built by merging these layers in order:
61
+
62
+
1. Base assistant
63
+
2. `member.assistantOverrides`
64
+
3. `squad.membersOverrides`
65
+
4. `call.squadOverrides`
68
66
69
67
Later layers win on conflicts. `variableValues` from all layers are merged separately.
Copy file name to clipboardExpand all lines: docs/learnings/structured-outputs.md
+5-5Lines changed: 5 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,11 +8,11 @@ Non-obvious behaviors and silent defaults for Vapi structured output extraction.
8
8
9
9
### Use single `type` values, not arrays
10
10
11
-
**Bad:**`type: [string, "null"]` — this can cause `TypeError: .toLowerCase is not a function`in the dashboard, and the backend uses only the **first** element of the array for extraction.
11
+
**Bad:**`type: [string, "null"]` — using an array for `type`is not supported and may cause errors in the dashboard and extraction pipeline. Vapi uses only the **first** element of the array.
12
12
13
13
**Good:**`type: string` — express nullability in the `description` instead (e.g., "Return null if not applicable").
14
14
15
-
### `wrapIfPrimitive` behavior
15
+
### Primitive schemas are auto-wrapped
16
16
17
17
Primitive schemas (`string`, `boolean`, `number`) are automatically wrapped in an object for OpenAI structured output mode:
If you omit `model` on a structured output, the default is:
36
+
If you omit `model` on a structured output, the default (as of this writing) is:
37
37
```yaml
38
38
model:
39
39
provider: openai
@@ -42,9 +42,9 @@ model:
42
42
maxTokens: 4000
43
43
```
44
44
45
-
Fallback sequence: your configured model → `gpt-4.1` → `gemini-2.5-flash`.
45
+
Fallback sequence: your configured model → `gpt-4.1` → `gemini-2.5-flash`. These defaults may change over time — check the API reference for the current default.
46
46
47
-
For multimodal extraction (`messages-with-audio`), the default is **Gemini 2.5 Pro**.
47
+
For multimodal extraction (`messages-with-audio`), the default is **Gemini 2.5 Pro** (as of this writing).
Copy file name to clipboardExpand all lines: docs/learnings/tools.md
+18-18Lines changed: 18 additions & 18 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@ Non-obvious behaviors and silent defaults for Vapi tool types.
10
10
11
11
**What you might expect:**`function.parameters` and `body` are two independent schemas — one for the LLM, one for the HTTP request.
12
12
13
-
**What actually happens:**The backend **overwrites**`function.parameters` with a copy of `body`during normalization. Whatever you define in `body.properties` becomes the tool schema the LLM sees. Any `function.parameters` you set on an apiRequest tool is **silently ignored**.
13
+
**What actually happens:**Vapi **overwrites**`function.parameters` with a copy of `body`when the tool is processed. Whatever you define in `body.properties` becomes the tool schema the LLM sees. Any `function.parameters` you set on an apiRequest tool is **silently ignored**.
14
14
15
15
**Recommendation:** Only define your schema in `body`. Do not set `function.parameters` on apiRequest tools.
16
16
@@ -28,19 +28,19 @@ If you omit `method`, it defaults to `POST` — not `GET`.
28
28
29
29
### API response must be JSON
30
30
31
-
On a successful HTTP response (2xx), the backend calls `response.json()`. If your API returns non-JSON (plain text, HTML, XML), the tool call will error.
31
+
On a successful HTTP response (2xx), Vapi expects a JSON body. If your API returns non-JSON (plain text, HTML, XML), the tool call will error.
32
32
33
33
### `function.strict` is always cleared
34
34
35
-
The backend sets `strict: undefined` on apiRequest tools during normalization. If you need strict schema enforcement from the LLM provider, use a `function` type tool instead.
35
+
Vapi removes `strict` from apiRequest tools when they are processed. If you need strict schema enforcement from the LLM provider, use a `function` type tool instead.
36
36
37
37
### `async` has no effect
38
38
39
-
The backend forces `async: undefined` on apiRequest tools. They always execute synchronously (HTTP call → wait for response → return to LLM).
39
+
Vapi ignores `async` on apiRequest tools. They always execute synchronously (HTTP call → wait for response → return to LLM).
40
40
41
41
### Credential fallback behavior
42
42
43
-
If `credentialId` is set on the tool, that specific credential is used. If omitted, the backend uses the **first**`webhook` or `custom-credential` it finds on the call's credential list. If you have multiple webhook credentials, always set `credentialId` explicitly.
43
+
If `credentialId` is set on the tool, that specific credential is used. If omitted, Vapi picks one from the call's available credentials automatically. If you have multiple webhook credentials, always set `credentialId` explicitly to avoid ambiguity.
44
44
45
45
---
46
46
@@ -50,7 +50,7 @@ If `credentialId` is set on the tool, that specific credential is used. If omitt
50
50
51
51
**What you might expect:** A function tool without `server.url` fails at runtime.
52
52
53
-
**What actually happens:**The backend falls back through a hierarchy: tool `server.url` → assistant `server.url` → phone number `server.url` → org `server.url`. Omitting `server` on the tool is valid if the assistant or org has a server URL configured.
53
+
**What actually happens:**Vapi falls back through a hierarchy: tool `server.url` → assistant `server.url` → phone number `server.url` → org `server.url`. Omitting `server` on the tool is valid if the assistant or org has a server URL configured.
54
54
55
55
**Recommendation:** Set `server.url` on the tool for clarity, or document which level provides the webhook URL.
56
56
@@ -66,9 +66,9 @@ If `credentialId` is set on the tool, that specific credential is used. If omitt
66
66
When `strict: true` is set on a function tool:
67
67
- Every `object` node must have `additionalProperties: false`
68
68
- Every property must be listed in `required`
69
-
-The org must have the `ENABLE_TOOL_STRICT_MODE` feature flag enabled
69
+
-Strict mode must be enabled for your organization
70
70
71
-
If the schema doesn't meet these requirements, `strict`is silently stripped and a warning is logged.
71
+
If the schema doesn't meet these requirements, `strict`may be silently disabled for that tool.
72
72
73
73
---
74
74
@@ -131,7 +131,7 @@ If you don't set `function.name`:
131
131
132
132
### Destination resolution is fuzzy
133
133
134
-
The backend tries multiple matching strategies for the model's destination argument:
134
+
Vapi tries multiple matching strategies for the model's destination argument:
135
135
1. Match by UUID (`assistantId`, `squadId`)
136
136
2. Match by name (`assistantName`, squad `name`)
137
137
3. Fall back to first destination if argument is `"dynamic"` or missing
@@ -145,15 +145,15 @@ The backend tries multiple matching strategies for the model's destination argum
145
145
146
146
### Execution timeout is capped at 30 seconds
147
147
148
-
Even if you set `timeoutSeconds: 60`, the backend caps it at `Math.min(timeoutSeconds ?? 30, 30)`.
148
+
Even if you set `timeoutSeconds: 60`, the effective timeout is capped at **30 seconds**.
149
149
150
150
### Code must be valid top-level async JavaScript
151
151
152
-
Code runs in an E2B sandbox as `node /tmp/code.js`. The code receives `args` (tool arguments) and `env` (environment variables) as globals. Return values are captured from the resolved promise.
152
+
Code runs in a managed sandbox as Node.js. The code receives `args` (tool arguments) and `env` (environment variables) as globals. Return values are captured from the resolved promise.
153
153
154
154
### Static validation checks `args.*` and `env.*` references
155
155
156
-
When saving a code tool, the backend parses your code and validates that every `args.foo` reference has a matching property in `function.parameters.properties`, and every `env.BAR` reference has a matching key in `environmentVariables`. Mismatches are rejected.
156
+
When saving a code tool, Vapi parses your code and validates that every `args.foo` reference has a matching property in `function.parameters.properties`, and every `env.BAR` reference has a matching key in `environmentVariables`. Mismatches are rejected.
157
157
158
158
### `function.strict` is cleared (like apiRequest)
159
159
@@ -195,11 +195,11 @@ After one delayed message plays, another won't play for at least 2.5 seconds —
195
195
196
196
| Tool type |`strict` behavior |
197
197
|-----------|-------------------|
198
-
|`function`|**Preserved** — passed to LLM provider if org has `ENABLE_TOOL_STRICT_MODE`|
199
-
|`apiRequest`|**Cleared**to `undefined` during normalization|
200
-
|`code`|**Cleared**to `undefined` during normalization|
201
-
|`transferCall`|**Cleared**to `undefined` during normalization|
202
-
|`endCall`|**Cleared**to `undefined` during normalization|
203
-
|`handoff`|**Cleared**to `undefined` during normalization|
198
+
|`function`|**Preserved** — passed to LLM provider if strict mode is enabled for your org|
199
+
|`apiRequest`|**Removed**when the tool is processed|
200
+
|`code`|**Removed**when the tool is processed|
201
+
|`transferCall`|**Removed**when the tool is processed|
|`server.timeoutSeconds`| Webhook delivery timeout (status-update, end-of-call-report, etc.) |Platform default (see API reference)|
28
+
|`tool.timeoutSeconds` (apiRequest) | Individual tool HTTP call timeout |Platform default (see API reference)|
29
29
30
30
They don't inherit from each other.
31
31
32
32
---
33
33
34
34
## Unreachable Server Behavior
35
35
36
-
-**Missing `server.url`:**Returns empty object, no network call. No error.
37
-
-**Server returns error:**Error is logged and returned. For `tool-calls`message type, the backend **still attempts to parse** the error response body for tool results.
38
-
-**High failure rate:**A circuit breaker kicks in — "noisy" message types (frequent events like `speech-update`) are aborted when the error rate exceeds a threshold. Critical messages like `end-of-call-report` continue attempting.
36
+
-**Missing `server.url`:**No network call is made. No error is raised.
37
+
-**Server returns error:**The error is logged and returned to the platform. For `tool-calls`events, Vapi still attempts to parse the error response body for tool results.
38
+
-**High failure rate:**Under sustained delivery failures, Vapi may reduce delivery of high-frequency events (like `speech-update`) while still attempting critical events (like `end-of-call-report`).
39
39
40
40
---
41
41
42
42
## Credential Resolution
43
43
44
-
For server webhooks:
45
-
1. If `server.credentialId` is set → use that specific credential
46
-
2. Else → use the first `custom-credential` on the call
47
-
3. Else → use the first `webhook` credential on the call
44
+
If `credentialId` is set on the server or tool, that specific credential is used. If omitted, Vapi picks one from the call's available credentials automatically.
48
45
49
-
For apiRequest tools:
50
-
1. If `tool.credentialId` is set → use that specific credential
51
-
2. Else → use the first `webhook` or `custom-credential` on the call
52
-
53
-
**Recommendation:** Always set `credentialId` explicitly when multiple credentials exist to avoid ambiguous fallback.
46
+
**Recommendation:** Always set `credentialId` explicitly when multiple credentials exist to avoid ambiguous selection.
0 commit comments