API Call Actions
API call actions let your agent fetch data or perform operations in your external systems during a conversation — for example, looking up order details, checking account balances, or creating support tickets.
Name and description
- Name: A clear, descriptive identifier (e.g.,
get_order_status,create_support_ticket). Names can only contain letters, numbers, underscores, and hyphens. - Description: Explain what this action does and when the agent should use it.
The agent uses the name and description to decide when to call the action, so be specific.
Example:
Name: get_order_status
Description: Retrieves the current status of a customer's order.
Use this when the customer asks about their order or wants to know when it will arrive.
Requires an order number.
Shared or agent-specific
API call actions can live in two places:
- Shared under a Custom API integration. The action inherits the integration's per-environment base URL and auth, and its URL field is interpreted as a path relative to that base URL. Any agent can link the action.
- Agent-specific on a single agent. The action carries its own full URL, auth, and headers. Use this for one-off calls that don't justify creating an integration.
The rest of this page describes the fields you'll see in either location.
URL
The full URL of your API endpoint, or — for shared actions under a Custom API integration — the path appended to the integration's base URL for the conversation's environment.
GET request:
https://api.yourcompany.com/orders
POST request:
https://api.yourcompany.com/support/tickets
URL templates
Use {{.parameters.X}} to insert collected parameter values into the URL path. These use the same delimiter syntax as request body templates, but only simple field access is supported — conditionals, functions, and control flow are not available in URL templates.
https://api.yourcompany.com/categories/{{.parameters.category_id}}/sections
When the action executes, {{.parameters.category_id}} is replaced with the collected value (e.g., https://api.yourcompany.com/categories/42/sections). Values are automatically URL-encoded for safe inclusion in path segments. Parameters used in the URL template are automatically removed from query params or the request body to prevent duplication. Every {{.parameters.X}} in the URL must have a matching property in the parameters schema.
In Studio, type @ in the URL field to pick from defined parameters. Inserted parameters appear as chips within the field.
After URL template substitution, remaining parameters are handled based on the HTTP method:
- GET and DELETE: Parameters are automatically appended as query parameters (e.g.,
?order_number=ORD-12345) - POST, PUT, and PATCH: Parameters are sent in the request body (see Request body template below)
For example, a GET request to https://api.yourcompany.com/orders with parameter {"order_number": "ORD-12345"} becomes https://api.yourcompany.com/orders?order_number=ORD-12345 at runtime.
Array-typed parameters are encoded as repeated keys — for example, {"tag": ["urgent", "billing"]} becomes ?tag=urgent&tag=billing. Nested objects and nested arrays are not supported as query parameter values.
HTTP method
Select the appropriate HTTP method for your API endpoint:
| Method | Use for |
|---|---|
GET | Retrieve information |
POST | Create new records or submit data |
PUT | Update existing records completely |
PATCH | Update specific fields |
DELETE | Remove records |
Headers
Add HTTP headers your API requires. Two types are supported:
Static value headers — For non-sensitive values like content types:
Content-Type: application/jsonX-API-Version: 1.0
Secret reference headers — For sensitive values like API keys and tokens. Select an existing secret instead of hardcoding the value. The secret is resolved at request time and never exposed in logs.
Secrets live under Secrets in the main menu. They support per-environment values so you can use different API keys for development, staging, and production.
For shared actions, the parent Custom API integration can also define headers that apply to every action in an environment. When an action sets a header with the same name as one of those shared headers, the action's value takes precedence.
Authentication connectors
For common authentication patterns, use an auth connector instead of manually configuring headers. Auth connectors handle credential management automatically and support token refresh for OAuth flows.
You select an auth connector from the auth picker on the action — or, for shared actions, on the parent Custom API integration. Each auth connector supports per-environment configuration, so the correct credentials are automatically resolved based on the environment of the conversation.
Bearer token — Adds an Authorization: Bearer <token> header to each request.
API key — Sends an API key via header, query parameter, or cookie.
| Location | Result |
|---|---|
| Header | X-API-Key: your-key |
| Query | ?api_key=your-key |
| Cookie | api_key=your-key |
OAuth 2.0 Client Credentials — Automatically fetches and refreshes access tokens using the OAuth 2.0 client credentials flow. Tokens are cached and refreshed when they expire. This is the recommended approach for service-to-service authentication.
Basic authentication — Adds an Authorization: Basic <credentials> header using username and password.
| Scenario | Recommendation |
|---|---|
| Standard auth patterns (Bearer, API key, Basic, OAuth2) | Use auth connector |
| Custom header names or formats | Use secret-based headers |
| Multiple auth headers needed | Combine auth connector with headers |
Parameters schema
Define the parameters your API expects using JSON Schema format. This tells the agent what information to collect from the customer before calling the action.
Example schema for getting order status:
{
"type": "object",
"properties": {
"order_number": {
"type": "string",
"description": "The customer's order number"
}
},
"required": ["order_number"]
}
Example schema for creating a support ticket:
{
"type": "object",
"properties": {
"customer_email": {
"type": "string",
"description": "The customer's email address"
},
"issue_description": {
"type": "string",
"description": "Description of the technical issue"
},
"priority": {
"type": "string",
"enum": ["low", "medium", "high"],
"description": "Priority level of the issue"
}
},
"required": ["customer_email", "issue_description"]
}
The agent will automatically ask the customer for any required parameters it doesn't already have. Parameters not in the required array are optional. The enum on priority restricts that field to a fixed set of values, so the agent will only ever send low, medium, or high.
The agent uses parameter descriptions to understand what information to collect and how to ask for it. Be specific about the parameter's purpose, expected format, and provide examples where helpful. Clear descriptions lead to better data collection.
Restricting values
In addition to specifying the type of a property, you can specify a selection of additional constraints to further restrict the allowed values:
Supported string properties:
pattern: A regular expression that the string must match.format: Predefined formats for strings. Currently supported:date-timetimedatedurationemailhostnameipv4ipv6uuid
Supported number properties:
multipleOf: The number must be a multiple of this value.maximum: The number must be less than or equal to this value.exclusiveMaximum: The number must be less than this value.minimum: The number must be greater than or equal to this value.exclusiveMinimum: The number must be greater than this value.
Supported array properties:
minItems: The array must have at least this many items.maxItems: The array must have at most this many items.
Here is an example that combines several of these constraints to verify a customer's address and identity:
{
"type": "object",
"properties": {
"house_number": {
"type": "number",
"description": "The house number of the customer's address",
"minimum": 1
},
"postal_code": {
"type": "string",
"description": "Dutch postal code, e.g. 1011 AB",
"pattern": "^[0-9]{4} ?[A-Za-z]{2}$"
},
"date_of_birth": {
"type": "string",
"description": "Date of birth in YYYY-MM-DD format, e.g. 1985-03-21",
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
},
"email": {
"type": "string",
"description": "The customer's email address",
"format": "email"
}
},
"required": ["house_number", "postal_code", "date_of_birth"]
}
Here, house_number must be a positive number, postal_code and date_of_birth must match their regular expressions, and email must be a valid email address.
Lists and nested data
Use an array when the agent needs to collect a list of values. Set items to the type of each element:
{
"type": "object",
"properties": {
"order_numbers": {
"type": "array",
"description": "List of order numbers to check",
"items": { "type": "string" }
}
}
}
Use a nested object to group related fields together, for example when your API expects a structured payload:
{
"type": "object",
"properties": {
"customer": {
"type": "object",
"description": "Customer information",
"properties": {
"name": { "type": "string", "description": "Customer's full name" },
"email": { "type": "string", "description": "Customer's email address" }
},
"required": ["name", "email"]
}
}
}
Conditional values
When the allowed values for one field depend on the value of another, use if/then with allOf. This is useful when sub-categories or specific options are only valid for certain main categories.
{
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": ["billing", "technical"],
"description": "Main issue category"
},
"sub_category": {
"type": "string",
"description": "Specific issue type within the category"
}
},
"required": ["category", "sub_category"],
"allOf": [
{
"if": {
"properties": { "category": { "const": "billing" } }
},
"then": {
"properties": {
"sub_category": { "enum": ["invoice_dispute", "payment_failed", "refund_request"] }
}
}
},
{
"if": {
"properties": { "category": { "const": "technical" } }
},
"then": {
"properties": {
"sub_category": { "enum": ["login_issue", "performance", "bug_report"] }
}
}
}
]
}
When category is "billing", the agent will only accept sub_category values of "invoice_dispute", "payment_failed", or "refund_request". When category is "technical", only "login_issue", "performance", or "bug_report" are valid.
Request body template
For POST, PUT, and PATCH requests, you can optionally define a request body template to control exactly how parameters are sent to your API. If no template is provided, parameters are sent as a simple flat JSON object.
Template syntax:
Templates use a subset of Go template syntax. You can test templates using the Go Template Playground.
Accessing data:
{{.parameters.parameter_name}}— Parameters from the parameter schema{{.variables.variable_name}}— Conversation variables{{.static_variables.VARIABLE_NAME}}— Variables configured in Studio, filled in at conversation start based on the environment{{index .parameters "key"}}— Map or slice values by key or index{{ toJson .parameters.key }}: Renders any parameter as JSON. Required for array or object parameters, which otherwise do not produce valid JSON.toJsonemits the surrounding quotes for strings itself, so do not wrap it in"...".
For example, to send an array parameter items alongside the conversation ID:
{ "stellarId": "{{.variables.stellar.conversation_id}}", "items": {{ toJson .parameters.items }} }
If items is [{"id": "1"}, {"id": "2"}], the body becomes:
{ "stellarId": "23cf...", "items": [{"id":"1"},{"id":"2"}] }
Conditionals and scoping:
{{if .parameters.name}} ... {{end}}— Conditional blocks{{if .parameters.name}} ... {{else}} ... {{end}}— Conditional with fallback{{with .parameters.name}} ... {{end}}— Scope rebinding (also supports{{else}}){{$var := .parameters.name}}— Assign a value to a variable
Allowed functions:
| Function | Description |
|---|---|
eq | Equal ({{if eq .parameters.status "active"}}) |
ne | Not equal |
lt | Less than |
le | Less than or equal |
gt | Greater than |
ge | Greater than or equal |
and | Logical AND |
or | Logical OR |
not | Logical NOT |
index | Access map/slice values ({{index .parameters "key"}}) |
contains | Substring match, needle first ({{if contains "foo" .variables.x}}) |
hasPrefix | Prefix match, needle first ({{if hasPrefix "foo" .variables.x}}) |
hasSuffix | Suffix match, needle first ({{if hasSuffix "foo" .variables.x}}) |
lower | Lowercase a string ({{.variables.x | lower}}) |
toJson | Render a value (array, object, or scalar) as JSON ({{ toJson .parameters.items }}) |
The needle-first argument order for contains, hasPrefix, and hasSuffix matches common templating conventions and lets the functions compose in pipelines, for example {{.variables.x | lower | contains "foo"}}. All three predicates evaluate to false when either argument is empty or missing, so a mistyped or unset variable on either side fails closed instead of matching everything.
For security, the following Go template features are not supported:
{{range}}(loops){{template}}(nested template invocation){{define}}(template definitions){{block}}(block definitions)- Custom function calls beyond the allowed list above
Example: Custom request body structure
If your API expects a nested structure:
{
"order": {
"id": "{{.parameters.order_number}}",
"customerEmail": "{{.parameters.email}}"
},
"metadata": {
"conversationId": "{{.variables.stellar.conversation_id}}",
"source": "voice_agent"
}
}
Example: Conditional fields
Include fields only when a parameter has a value:
{
"name": "{{.parameters.name}}",
{{- if .parameters.email}}
"email": "{{.parameters.email}}",
{{- end}}
"source": "voice_agent"
}
Example: Map access with fallback
Use index with with to safely access nested values:
{{with index .parameters "preferred_name"}}{{.}}{{else}}Customer{{end}}
Without a template (default):
{
"order_number": "ORD-12345",
"email": "customer@example.com"
}
Use templates when your API requires a specific nested structure, you need to include conversation variables, you want to add static fields alongside dynamic parameters, or your API expects a different field naming convention. For simple APIs that accept flat JSON, leave this blank.
If a referenced parameter or variable is missing, the action returns an error to the agent rather than failing silently. The agent can then ask the user for the missing information.
Response processing
By default the agent sees the full API response and can use it immediately in the conversation. You can restrict what the agent sees using fields visible to AI rules. For example, if your API returns:
{
"order_number": "12345",
"status": "shipped",
"tracking_number": "TRACK123",
"estimated_delivery": "2024-03-15"
}
The agent might say: "Your order #12345 has shipped! The tracking number is TRACK123, and it should arrive by March 15th."
A call is considered successful when the API returns a 2xx HTTP status code. Calls that return 4xx or 5xx responses, or fail due to network errors, are treated as unsuccessful. This distinction matters when using execution limits — only successful calls count toward the limit.
API calls have a 15-second timeout. If your API does not respond within this limit, the call is treated as unsuccessful and the agent is informed that the request timed out.
Output variables
Output variables extract specific values from API responses and store them as conversation variables for use in subsequent actions or prompts. This is useful for capturing data like customer IDs or tokens needed by later API calls.
For each output variable, configure:
- Variable name: The conversation variable name (e.g.,
customer_id,account.status). Use lowercase letters, numbers, underscores, and dots. - JSONPath expression: A JSONPath expression that identifies the value to extract. Must start with
$. - Sensitive: When enabled, the value is redacted in logs and conversation history.
- Expose to AI: When enabled, the extracted value is available to the AI assistant in the conversation.
JSONPath examples:
Given an API response {"data": {"id": "cust_123", "email": "user@example.com"}}:
| JSONPath | Extracted value |
|---|---|
$.data.id | "cust_123" |
$.data.email | "user@example.com" |
When enabled, the assistant can see and speak the extracted value in later turns. Keep it disabled for internal IDs or tokens that should only be used in subsequent API calls — the value will still be available for action parameters, just not visible to the AI.
Fields visible to AI
By default the agent receives the full API response body. When an API returns more data than the agent needs — or when you want to prevent the agent from seeing sensitive fields — you can add visible-field rules to restrict the view.
Each rule is either an include or exclude paired with a JSONPath expression:
- Include rules limit the agent to only the matched fields.
- Exclude rules hide specific fields from the agent.
Excludes always win. If a field is matched by both an include and an exclude rule, the field is hidden.
No rules configured means the agent sees the full response — the same as before visible-field rules were introduced.
If rules are configured but nothing matches (for example, the response shape doesn't contain the expected paths, or the API call itself returns an error), the agent sees only the HTTP status code. It never receives the raw response body in this case. This fail-closed behavior prevents accidental data exposure when a response structure changes.
JSONPath syntax — Rules use a subset of JSONPath. All expressions must start with $:
| Expression | Matches |
|---|---|
$.status | The status field at the root |
$.data.id | Nested field access |
$.messages[0] | First element of the messages array |
$.messages[*] | All elements of the messages array |
$.messages[*].text | The text field of every message |
Array slices (e.g. $.items[0:3]) and filter expressions (e.g. $.items[?(@.active)]) are not supported.
Example — hide sensitive fields:
Include: $.messages
Exclude: $.messages[*].pii
The agent can see the messages array but the pii field within each message is removed.
Example — expose only what the agent needs:
Include: $.order.status
Include: $.order.estimated_delivery
The agent sees only the order status and estimated delivery date, even if the response contains payment details, internal IDs, or other fields.
Output variables are extracted from the raw API response before visible-field rules are applied. If you expose an output variable (via Expose to AI) whose JSONPath reads a field that an exclude rule hides, the agent still receives that value through the variable. To prevent a sensitive field from reaching the agent entirely, disable Expose to AI on any output variable that reads it, in addition to adding an exclude rule.
Fields visible to AI rules apply to voice agent api_call actions. They take effect on both agent-specific actions and shared actions from Custom API integrations.
Next steps
- Set up a Custom API integration to share base URLs, auth, and actions across agents
- Use output variables to chain actions together for multi-step workflows
- Test your action in the Playground