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