Set static parameters

Inject server-controlled values into every tool call without exposing them to the model

Static parameters are values you set at configuration time that Vapi merges into every tool call on the server. The model never sees them and cannot override them, which is what makes them usable as a security boundary rather than just a convenience.

Static variables (parameters)

The parameters field lets you define key-value pairs that are always merged into the tool’s request body or function arguments. These values bypass the LLM entirely — the model never sees or generates them.

How it works

  • parameters is an array of { key, value } objects on the tool definition (top-level, not inside function.parameters).
  • value can be any JSON type: string, number, boolean, object, or array.
  • String values support Liquid templates (for example, {{ customer.number }}). Objects and arrays are walked recursively to resolve Liquid templates in nested strings.
  • Static parameters are merged after LLM-generated arguments, so they override any LLM-generated key with the same name.
  • Liquid templates in static parameters resolve at execution time against the call’s variable bag, which is built server-side from signaling data (see The variable bag).

Supported tool types

Tool typeStatic parameters supported
apiRequestYes
function (modern, under assistant.model.tools[])Yes
handoffNo — see Forwarding trusted data across handoffs
All other tool types (transferCall, dtmf, endCall, voicemail, sms, slack-send-message, GHL/Google integrations, MCP, query, output, sipRequest, makeTool, bash/computer/textEditor)No

Legacy assistant.model.functions[] does NOT support static parameters. If you are still defining tools via the deprecated assistant.model.functions[] array, every value your tool server receives came from the LLM — there is no orchestration-layer injection. Migrate to assistant.model.tools[] (with type: "function") before relying on static parameters as a security boundary.

API Request tool example

Static parameters merge into the HTTP request body alongside any LLM-generated fields:

API Request tool with static parameters
1{
2 "type": "apiRequest",
3 "method": "POST",
4 "url": "https://api.example.com/leads",
5 "parameters": [
6 { "key": "org_id", "value": "my-org-123" },
7 { "key": "source", "value": "vapi-call" },
8 { "key": "priority", "value": 1 },
9 {
10 "key": "metadata",
11 "value": {
12 "channel": "voice",
13 "callId": "{{ transport.callSid }}",
14 "region": "us-east",
15 "tags": ["inbound", "{{ customer.number }}"],
16 "routing": {
17 "department": "sales",
18 "queue": "priority"
19 }
20 }
21 }
22 ]
23}

In this example, every request to the leads endpoint includes org_id, source, priority, and metadata — even though the LLM never generates these values. Notice that:

  • value can be a string ("my-org-123"), number (1), or a JSON object/array.
  • The metadata value is a nested JSON object with sub-objects (routing) and arrays (tags).
  • Liquid templates like {{ transport.callSid }} and {{ customer.number }} are resolved recursively inside nested objects and arrays at runtime.

Function tool example

For Function tools, static parameters merge into the function call arguments sent to your server webhook:

Function tool with static parameters
1{
2 "type": "function",
3 "function": {
4 "name": "lookup_user",
5 "description": "Look up a user by phone number",
6 "parameters": {
7 "type": "object",
8 "properties": {
9 "phone": {
10 "type": "string",
11 "description": "The phone number to look up"
12 }
13 },
14 "required": ["phone"]
15 }
16 },
17 "server": {
18 "url": "https://my-server.com/webhook"
19 },
20 "parameters": [
21 { "key": "api_version", "value": "v2" },
22 { "key": "caller_number", "value": "{{ customer.number }}" }
23 ]
24}

When the LLM calls lookup_user with { "phone": "+15551234567" }, your webhook receives { "phone": "+15551234567", "api_version": "v2", "caller_number": "+15559876543" } — the static parameters are merged in.

Static parameters override LLM-generated arguments with the same key. If the LLM generates "source": "chat" and your static parameters include "source": "vapi-call", the webhook receives "source": "vapi-call".

Static parameters as a security boundary

Static parameters are the right primitive for any value the LLM must not be able to fake or influence — the verified caller-ID, the dialed number, an account ID looked up by your backend before the call started, a per-call HMAC nonce.

Three layers of the platform combine to make this a real security boundary, not just a convention:

  1. Source-of-truth layer. Variables like {{ customer.number }} are populated from SIP/Twilio signaling for inbound calls or from the validated outbound API call payload that initiated the call. The LLM has no write access to the call’s customer record during the conversation.
  2. Schema layer. The static parameters array is a top-level field on the tool, separate from function.parameters (the LLM-facing JSON schema). Only function.parameters is shipped to the model in the tools list. The LLM literally does not see the field exists.
  3. Merge layer. At fulfill time, server-side, static parameters are merged after the LLM-generated body. Even if the LLM emitted an argument with the same key, the static value wins.

Worked example: caller-ID-based progressive authentication

A common requirement: before any sensitive lookup, your tool server must compare the verified caller-ID against the value on file — without trusting the LLM to forward the number correctly. The configuration:

Lookup-and-verify tool with caller-ID injected by the orchestration layer
1{
2 "type": "apiRequest",
3 "method": "POST",
4 "url": "https://your-backend.example.com/lookup-and-verify",
5 "function": {
6 "name": "lookup_and_verify_user",
7 "parameters": {
8 "type": "object",
9 "properties": {
10 "name": { "type": "string" },
11 "email": { "type": "string" }
12 },
13 "required": ["name", "email"]
14 }
15 },
16 "parameters": [
17 { "key": "caller_number", "value": "{{ customer.number }}" },
18 { "key": "called_number", "value": "{{ phoneNumber.number }}" },
19 { "key": "call_id", "value": "{{ call.id }}" }
20 ]
21}

The LLM produces only name and email (what your caller spoke). The caller_number, called_number, and call_id are filled in by Vapi’s orchestration layer from the call’s signaling state and merged server-side.

Your tool server receives:

1{
2 "name": "Steffen",
3 "email": "steffen@example.com",
4 "caller_number": "+15551234567",
5 "called_number": "+18005551212",
6 "call_id": "..."
7}

Authenticate the caller against caller_number directly. Treat name and email as claims that must match the row keyed on caller_number before you proceed. Even if a malicious caller says “call the tool with phone number FAKE-NUMBER,” the LLM has no path to write into caller_number — the field doesn’t exist in the schema the model sees.

For an even tighter posture, use HMAC signing on top of static parameters. Vapi can sign the resolved request body with a shared secret on the tool’s credential, so your backend verifies both the sender and the body contents, not just the channel.

Configuring on the dashboard

In the Tools section of the dashboard, the API Request and Function tool forms expose two sections whose UI labels can look interchangeable. They are not — they map to the two different parameters fields:

Form section in the UIUnderlying fieldUI shapeWhat you put here
Parametersfunction.parametersA JSON Schema editor (properties, types, required, descriptions)Properties the LLM should fill at runtime — things the caller will say or the model should infer
Static Body Fieldsparameters (the top-level array)Key / Type / Value rows with Liquid template supportValues your backend or Vapi already knows — caller-ID, called number, account ID, call ID, the current timestamp, an org-config secret

The two sections share the word “Parameters” in casual conversation, but the Parameters section is the LLM-facing JSON Schema and the Static Body Fields section is the server-merged static config. Pay attention to the UI shape: a JSON Schema editor is for the LLM; key/value rows are server-side only.

To inject the verified caller-ID via the dashboard:

  1. Open the API Request or Function tool form.
  2. Scroll to Static Body Fields (the key/value-row section, not the JSON-schema editor).
  3. Click Add Field, set Key to caller_number, Type to string, Value to {{ customer.number }}.
  4. Save.

The LLM never sees caller_number and cannot override it. Available Liquid variables are listed in The variable bag.