> ## Documentation Index
> Fetch the complete documentation index at: https://apidocs.vodex.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Prompt variables

> Every {{token}} an agent can say, where its value comes from, and how each way of calling fills it

Write a variable in double braces, `{{first_name}}`, in any line the agent
says: the system prompt, the first message, the end-call and silence messages,
the voicemail message, the built-in tools' spoken lines and each disclosure.
Names use letters, digits, `_` and `.`, and spaces inside the braces are
ignored, so `{{ first_name }}` is the same token. Single braces are left alone,
so JSON examples in a prompt stay as written.

Every name in Vodex is **snake\_case**: contact fields, custom fields, caller
verification sources and the examples on this page. Use the same spelling in
your prompts, your CSV headers and your API requests and they will line up
everywhere.

> [!WARNING]
>
> A token with no value is said **as written**. The agent reads out
> `Hi {{first_name}}` with the braces, which is why campaigns and batches refuse
> a contact or row that is missing one rather than calling with a gap. A call
> placed through `POST /calls` is not refused, so check your variables there.

## Contact fields

A contact fills these on its own. In a campaign or a **Call now** from the
contact's page, `{{first_name}}` needs no mapping. The **Contact details** tab
shows each token beside its field.

| Token | Contact details field | API field | What the agent gets |
| - | - | - | - |
| `{{first_name}}` | First name | `firstName` | |
| `{{last_name}}` | Last name | `lastName` | |
| `{{full_name}}` | The contact's name | `displayName` | First and last name joined, unless a display name was set |
| `{{email}}` | Email | `email` | The field, or the contact's primary email channel when the field is empty |
| `{{account_number}}` | Account number | `accountNumber` | |
| `{{date_of_birth}}` | Date of birth | `dateOfBirth` | `2000-06-01`. Tell the agent how to say it |
| `{{ssn_last4}}` | SSN | `ssnLast4` | The four digits on file |
| `{{timezone}}` | Time zone | `timezone` | An IANA name, such as `Asia/Kolkata` |
| `{{address}}` | The whole Address section | | `88 Union Ave, Floor 2, Memphis, TN 38103, US`, leaving out any part with no value and any place line 1 already names. Each part reads a same-named custom field first, as its own token does. Empty without an Address (line 1) |
| `{{address_line1}}` | Address | `addressLine1` | |
| `{{address_line2}}` | Address line 2 | `addressLine2` | |
| `{{city}}` | City | `city` | |
| `{{state}}` | State | `state` | |
| `{{postal_code}}` | Postal code | `postalCode` | |
| `{{country}}` | Country | `country` | |
| `{{phone}}` | | | The number this call is dialling |

The **API field** column is the name the REST API uses for the same value, in
request bodies and as a campaign source (`field:firstName`). It stays camelCase
like the rest of the API. Only the token is snake\_case. The
[contact import template](https://apidocs.vodex.ai/api-reference/contacts/a-csv-template-with-the-recognised-headers.md)
uses the tokens as its column headers, so a file built from it, a prompt and the
Contact details tab all use the same names.

A value is only fetched when the prompt (or caller verification) asks for it, so
a script that never mentions `{{ssn_last4}}` never puts it into a call.

### Other spellings

Contact field tokens ignore case and punctuation, so `{{firstName}}`,
`{{First_Name}}` and `{{first_name}}` all fill from First name. Prompts written
before snake\_case became the documented spelling keep working.

They also answer to the words a CSV header uses for the same field:
`{{name}}`, `{{surname}}`, `{{zip}}`, `{{postcode}}`, `{{dob}}`, `{{ssn}}`,
`{{street}}`, `{{town}}`, `{{province}}`, `{{tz}}`, `{{phone_number}}`.

A few words are deliberately **not** recognised, because each one could mean
several things in a sentence: `{{number}}`, `{{account}}`, `{{reference}}`,
`{{ref}}`, `{{unit}}`, `{{apt}}`, `{{suite}}`, `{{region}}`, `{{county}}`. A labelled number such as `{{mobile}}` or `{{work_phone}}` is not
recognised either, since the call may be dialling a different one. Use
`{{phone}}`.

> [!NOTE]
>
> This forgiving match applies **only** to contact fields. Variables in an API
> request and columns in a batch CSV are matched by their exact name: a CSV
> column called `first_name` fills `{{first_name}}` and nothing else.

### Fields that never fill themselves

External id, Status, Notes, Tags, the assigned user, the do-not-contact
fields, consent, the created and updated times, and the last-contacted fields
are not tokens. They are ids, workflow state or notes written about the
contact, not things to say to them. A campaign can still map one explicitly,
for example `"stage": "field:status"`.

## Custom fields

A custom field is `{{key}}`, where the key is the one shown under the field on
the **Custom details** tab: `attr:renewal_date` is `{{renewal_date}}`. Keys are
made from the label when the field is created, so "Renewal Date" becomes
`renewal_date`, already in snake\_case.

Custom fields fill on their own in the same places contact fields do, and match
ignoring case and punctuation in the same way. When a custom field and a
contact field share a name, **the custom field wins** whenever it has a value: a
`city` custom field fills `{{city}}` instead of the City column. The Contact
details tab warns beside any field this happens to for that contact.

A checkbox is said as "yes" or "no", and a list as its items joined by commas.

## How each way of calling fills them

### Campaigns

At launch, each contact's variables are built in this order, each layer over
the one before:

1. Their contact fields, for every token the prompt says.
2. Their custom fields.
3. The campaign's `variables` map.
4. `tz`, set from the contact's time zone. The calling window reads it.

The map is only needed for a name the contact does not already answer to. Each
entry says where the value comes from:

| Source | Fills from |
| - | - |
| `field:<API field>` | A contact field, including the ones that never fill themselves (`field:status`) |
| `attr:<key>` | A custom field |
| `stat:attemptsTotal` | How many calls this contact has had. `0` if none |
| `stat:attempts7d` | How many calls in the last seven days |
| Any other text | Said as written, the same for everyone |

```json theme={null}
"variables": {
  "customer": "field:displayName",
  "balance": "attr:current_balance",
  "company": "Acme Collections"
}
```

A name in the map uses **only** its mapping. If the mapped source is empty for
a contact, that contact is `rejected` at launch. Vodex does not fall back to a
field of the same name. The sources a campaign can map are
[listed by the API](https://apidocs.vodex.ai/api-reference/campaigns/what-a-prompt-variable-can-be-filled-from.md),
along with the tokens a contact fills on its own.

### Call now from a contact

The contact's fields and custom fields fill the prompt the same way, with
nothing to map. A token they have no value for is said as written.

### `POST /calls`

Set `contactRef` to a contact's id and any variable the request leaves blank is
filled from that contact. The order, highest first:

1. The request's `variables`. Blank values are ignored.
2. The contact named by `contactRef`: custom fields, then contact fields.
3. The agent's saved default values.

```json theme={null}
{
  "assistantId": "3f9c…",
  "toNumber": "+14155551234",
  "fromNumberId": "…",
  "contactRef": "<contact id>",
  "variables": { "balance": "412.50" }
}
```

With the prompt `Hi {{first_name}}, your balance is {{balance}}`, the name
comes from the contact and the balance from the request. Request variables
match by exact name, so send `first_name`, not `firstName`.

### Batches

Every CSV column except `to`, `rowRef`, `assistantId` and `from` becomes a
variable named exactly as its header, so a `first_name` column fills
`{{first_name}}`. A row missing a variable the prompt says is refused at upload.
Batches do not read your contacts. Add a `tz` column for calling windows.

```csv theme={null}
to,first_name,balance,tz
+14155551234,Priya,412.50,America/Los_Angeles
```

### Test calls from the agent page

Values typed beside each variable on the agent page are saved with the agent as
its defaults. Test calls from that page use them. On real calls they are the
last fallback, below the request and the contact, and they never count for a
campaign or batch row, which must carry its own values. They are never used for
caller verification answers.

## Caller verification

Each verification question names the variable that holds the right answer, its
**Answer comes from**. On a campaign or a `contactRef` call that variable is
filled from the contact like any other, so `date_of_birth` checks against the
contact's date of birth and `full_name` against their name. On a batch it is a
CSV column, and on an API call a request variable.

The answers are kept out of the call: a question's source cannot also appear as
a `{{token}}` in the prompt, under its own name or any other spelling of the
same field (`{{zip}}` for a `postal_code` answer), and the answers are left off
the call record and the webhook. The one exception is the name used for confirmation, which the
agent has to say. Write it as `{{full_name}}`.

`{{address}}` leaves out any part that is a question's answer, so verifying on
`postal_code` gives `88 Union Ave, Floor 2, Memphis, TN, US`. When the answer is
already written into the address itself, in line 1, line 2 or a custom
`address` field, `{{address}}` is left out altogether. When the question is the
address itself, its answer is the two street lines, which is what a caller says
back, and `{{address_line1}}`, `{{street}}` and `{{address_line2}}` are kept out
of the prompt as well.

## SMS and email templates

In-call SMS and email templates use the same variables as the call. They accept
`{first_name}` as well as `{{first_name}}`, ignore case, and drop a token with
no value instead of sending the braces. When two variables differ only in case,
the one spelled exactly as the token wins. `{{full_name}}` and expressions such
as `{{ amount + 25 }}` are worked out the same way as in the prompt. Dates such
as `{{today}}` use the same timezone the call's prompt does, the contact's `tz`
or the batch's calling window; with no timezone at all a template leaves the
date out rather than send the UTC one. A template only has the variables the
call already has, so a contact field mentioned only in the template, never in
the prompt, is blank.

## Next

> [card] [Contacts](https://apidocs.vodex.ai/contacts.md)
>
> Importing people, audiences, and launching a campaign.

> [card] [Place a call](https://apidocs.vodex.ai/api-reference/calls/place-an-outbound-call.md)
>
> `variables` and `contactRef` on a single call.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.