DataMap — Server-Side SWAIG API Passthrough
DataMap is the "no-webhook" mode of SWAIG. Instead of you hosting a function handler, you describe the API call once and SignalWire executes it on its infrastructure. The response is templated into a FunctionResult and spoken to the caller.
Use DataMap when:
- The integration is a straightforward HTTP call to a public or token-auth API.
- You want zero servers.
- The transformation from API response to spoken reply fits a template.
Skip DataMap when:
- You need to mutate database state.
- The handler needs custom auth flows (OAuth refresh, signed payloads).
- The logic needs branching beyond simple
expression()regex matches.
Variable substitution
| Pattern | Expands to |
|---|---|
| ${args.param} | Function argument value |
| ${enc:args.param} | URL-encoded argument |
| ${lc:args.param} | Lowercased argument |
| ${fmt_ph:args.phone} | Formatted phone number (E.164 → human-readable) |
| ${response.field} | API response field |
| ${response.arr[0]} | Array element from response |
| ${global_data.key} | Session-wide data |
| ${this.field} | Current item inside a foreach loop |
Modifiers chain right-to-left. ${enc:lc:args.city} lowercases first, then URL-encodes.
GET request
from signalwire import DataMap, FunctionResult
weather = (
DataMap("get_weather")
.description("Get current weather for a city")
.parameter("city", "string", "City name", required=True)
.webhook("GET", "https://api.weatherapi.com/v1/current.json?key=API_KEY&q=${enc:args.city}")
.output(FunctionResult(
"Weather in ${args.city}: ${response.current.condition.text}, ${response.current.temp_f}F"
))
.fallback_output(FunctionResult("Sorry, couldn't get weather for ${args.city}."))
)
agent.register_swaig_function(weather.to_swaig_function())
POST with body and foreach loop
foreach iterates over an array in the response and accumulates a template into a single output variable.
search_docs = (
DataMap("search_docs")
.purpose("Search documentation")
.parameter("query", "string", "Search query", required=True)
.webhook("POST", "https://api.docs.example.com/search",
headers={"Authorization": "Bearer TOKEN"})
.body({"query": "${args.query}", "limit": 3})
.foreach({
"input_key": "results",
"output_key": "formatted_results",
"max": 3,
"append": "- ${this.title}: ${this.summary}\n",
})
.output(FunctionResult("Found:\n${formatted_results}"))
.fallback_output(FunctionResult("Search unavailable."))
)
foreach parameters:
| Key | Notes |
|---|---|
| input_key | Array path in the response JSON. |
| output_key | Variable name where the concatenated string lands. |
| max | Stop after this many items. |
| append | Template per item, using ${this.field}. |
Expression-only (no HTTP)
For pure pattern matching with no API call, use .expression():
volume = (
DataMap("set_volume")
.description("Control audio volume")
.parameter("level", "string", "Volume level", required=True)
.expression("${args.level}", r"high|loud|up", FunctionResult("Volume increased"))
.expression("${args.level}", r"low|quiet|down", FunctionResult("Volume decreased"))
.expression("${args.level}", r"mute|off", FunctionResult("Audio muted"))
)
agent.register_swaig_function(volume.to_swaig_function())
Each .expression(value, regex, result) is evaluated in order. First match wins. Useful for IVR-like routing without a real backend.
Helper shortcuts
from signalwire import create_simple_api_tool, create_expression_tool
# Simple GET tool
weather_tool = create_simple_api_tool(
name="get_weather",
url="https://api.weather.com/v1/current?key=KEY&q=${args.location}",
response_template="Weather in ${args.location}: ${response.current.condition.text}",
parameters={"location": {"type": "string", "description": "City name", "required": True}},
)
# Pattern-matching tool
cmd_tool = create_expression_tool(
name="playback_control",
patterns={
"${args.command}": (r"play.*", FunctionResult("Playing.")),
},
parameters={"command": {"type": "string", "description": "Command", "required": True}},
)
These wrap the full DataMap builder when you only need a single GET or single regex tool.
Auth patterns
| Auth style | How |
|---|---|
| API key in URL | Inline: https://api.example.com/?key=ABC&q=${enc:args.q} |
| Bearer token | .webhook("GET", "...", headers={"Authorization": "Bearer TOKEN"}) |
| Static header | Same headers={} pattern |
| OAuth refresh / signed requests | Use a @tool handler, not DataMap |
Fallback behavior
.fallback_output(...) fires when:
- The HTTP call returns a non-2xx status.
- The response JSON doesn't contain the expected fields.
- A timeout (default ~10s) elapses.
Always provide a fallback. Without it, a failed call returns nothing and the AI invents a response.
Anti-patterns
- Putting secrets directly in
headersand checking the SWML into git — secrets land in version control. Inject at runtime. - Using DataMap for mutation endpoints (POST that creates records) — race conditions, no retry strategy.
- Foreach with
max > 10— spoken response gets unbearably long. - Skipping
fallback_output— AI hallucinates results when the API errors. - Mixing
%{...}(SWML) and${...}(DataMap) prefixes — DataMap is${...}only.