← Twilio Developer KitCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Twilio Developer Kit
Snapshot Sep 30, 2026 · 22:50 UTC · version 0.2.2
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"name": "twilio-taskrouter-routing",
"description": "Route tasks to agents using Twilio TaskRouter. Covers Workers, Task Queues, Workflows, Reservations, skills-based routing, and common gotchas (hyphen attributes, HAS operator, reservation cascade). Use this skill for any multi-agent contact center, support queue, or AI agent escalation routing.",
"included_files": [
{
"relative_path": "agents/openai.yaml",
"size_in_bytes": 231
}
],
"skill_md_contents": "---\nname: twilio-taskrouter-routing\ndescription: >\n Route tasks to agents using Twilio TaskRouter. Covers Workers, Task\n Queues, Workflows, Reservations, skills-based routing, and common\n gotchas (hyphen attributes, HAS operator, reservation cascade). Use this\n skill for any multi-agent contact center, support queue, or AI agent\n escalation routing.\n---\n\n## Overview\n\nTaskRouter is Twilio's skills-based routing engine. Instead of building custom queuing logic, you define Workers (agents), Task Queues (groups), and Workflows (routing rules). TaskRouter matches incoming tasks to the best available worker.\n\n```\nIncoming Task → Workflow (routing rules) → Task Queue (skill match) → Worker (agent)\n ↓\n Reservation\n (accept/reject)\n```\n\n**Common mistake:** Developers reinvent TaskRouter in custom Node.js — don't. If you're building skills-based routing, queue management, or agent assignment, use TaskRouter.\n\n---\n\n## Prerequisites\n\n- Twilio account — see `twilio-account-setup`\n- `TWILIO_ACCOUNT_SID` and `TWILIO_AUTH_TOKEN` — see `twilio-iam-auth-setup`\n- SDK: `pip install twilio` / `npm install twilio`\n- For voice routing: a Twilio phone number with webhook configured — see `twilio-voice-twiml`\n- For AI escalation: ConversationRelay with escalation tools — see `twilio-voice-conversation-relay`\n\n---\n\n## Quickstart\n\n**Step 1 — Create a Workspace**\n\nA Workspace is the top-level container for all TaskRouter resources.\n\n**Python**\n```python\nimport os\nfrom twilio.rest import Client\n\nclient = Client(os.environ[\"TWILIO_ACCOUNT_SID\"], os.environ[\"TWILIO_AUTH_TOKEN\"])\n\nworkspace = client.taskrouter.v1.workspaces.create(\n friendly_name=\"Support Center\",\n event_callback_url=\"https://yourapp.com/taskrouter-events\"\n)\n\nworkspace_sid = workspace.sid # WSxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nprint(workspace_sid)\n```\n\n**Node.js**\n```node\nconst twilio = require(\"twilio\");\nconst client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);\n\nconst workspace = await client.taskrouter.v1.workspaces.create({\n friendlyName: \"Support Center\",\n eventCallbackUrl: \"https://yourapp.com/taskrouter-events\",\n});\n\nconst workspaceSid = workspace.sid;\n```\n\n**Step 2 — Create Activities (agent states)**\n\n**Python**\n```python\n# Available — worker can receive tasks\navailable = client.taskrouter.v1.workspaces(workspace_sid).activities.create(\n friendly_name=\"Available\", available=True\n)\n\n# Offline — worker cannot receive tasks\noffline = client.taskrouter.v1.workspaces(workspace_sid).activities.create(\n friendly_name=\"Offline\", available=False\n)\n\n# On a task — worker is busy\non_task = client.taskrouter.v1.workspaces(workspace_sid).activities.create(\n friendly_name=\"On Task\", available=False\n)\n```\n\n**Step 3 — Create Workers (agents)**\n\n> **Security:** Always use `json.dumps()` (Python) or `JSON.stringify()` (Node.js) to construct attribute payloads. String interpolation is vulnerable to JSON injection.\n\n**Python**\n```python\nworker = client.taskrouter.v1.workspaces(workspace_sid).workers.create(\n friendly_name=\"Alice\",\n attributes='{\"skills\": [\"billing\", \"technical\"], \"languages\": [\"en\", \"es\"], \"department\": \"support\"}'\n)\n```\n\n**Node.js**\n```node\nconst worker = await client.taskrouter.v1.workspaces(workspaceSid).workers.create({\n friendlyName: \"Alice\",\n attributes: JSON.stringify({\n skills: [\"billing\", \"technical\"],\n languages: [\"en\", \"es\"],\n department: \"support\",\n }),\n});\n```\n\n**Step 4 — Create Task Queues**\n\n**Python**\n```python\n# Billing queue — matches workers with \"billing\" skill\nbilling_queue = client.taskrouter.v1.workspaces(workspace_sid).task_queues.create(\n friendly_name=\"Billing\",\n target_workers='skills HAS \"billing\"'\n)\n\n# Technical queue\ntech_queue = client.taskrouter.v1.workspaces(workspace_sid).task_queues.create(\n friendly_name=\"Technical\",\n target_workers='skills HAS \"technical\"'\n)\n\n# Catch-all queue\ndefault_queue = client.taskrouter.v1.workspaces(workspace_sid).task_queues.create(\n friendly_name=\"Default\",\n target_workers='1==1' # matches all workers\n)\n```\n\n**Step 5 — Create a Workflow (routing rules)**\n\n**Python**\n```python\nimport json\n\nworkflow_config = {\n \"task_routing\": {\n \"filters\": [\n {\n \"filter_friendly_name\": \"Billing\",\n \"expression\": \"department == 'billing'\",\n \"targets\": [\n {\"queue\": billing_queue.sid, \"timeout\": 120}\n ]\n },\n {\n \"filter_friendly_name\": \"Technical\",\n \"expression\": \"department == 'technical'\",\n \"targets\": [\n {\"queue\": tech_queue.sid, \"timeout\": 120}\n ]\n }\n ],\n \"default_filter\": {\n \"queue\": default_queue.sid\n }\n }\n}\n\nworkflow = client.taskrouter.v1.workspaces(workspace_sid).workflows.create(\n friendly_name=\"Support Routing\",\n configuration=json.dumps(workflow_config),\n assignment_callback_url=\"https://yourapp.com/assignment\"\n)\n```\n\n**Step 6 — Create a Task (from an incoming call)**\n\n**Python**\n```python\ntask = client.taskrouter.v1.workspaces(workspace_sid).tasks.create(\n attributes='{\"department\": \"billing\", \"caller\": \"+15558675310\", \"priority\": 1}',\n workflow_sid=workflow.sid\n)\n```\n\n**Step 7 — Handle the Assignment Callback**\n\nWhen TaskRouter finds a matching worker, it POSTs to your `assignment_callback_url`:\n\n**Python (Flask)**\n```python\n@app.route(\"/assignment\", methods=[\"POST\"])\ndef assignment():\n task_sid = request.form[\"TaskSid\"]\n worker_sid = request.form[\"WorkerSid\"]\n reservation_sid = request.form[\"ReservationSid\"]\n\n # Option A: Dequeue to the worker's phone\n return jsonify({\n \"instruction\": \"dequeue\",\n \"from\": \"+15551234567\", # your Twilio number\n \"post_work_activity_sid\": available_activity_sid\n })\n\n # Option B: Conference the caller and agent\n # return jsonify({\n # \"instruction\": \"conference\",\n # \"from\": \"+15551234567\",\n # \"post_work_activity_sid\": available_activity_sid\n # })\n```\n\n**Node.js (Express)**\n```node\napp.post(\"/assignment\", (req, res) => {\n res.json({\n instruction: \"dequeue\",\n from: \"+15551234567\",\n post_work_activity_sid: availableActivitySid,\n });\n});\n```\n\n---\n\n## Key Patterns\n\n### Skills-Based Routing\n\nMatch tasks to workers based on attributes:\n\n| Worker expression | Matches |\n|-------------------|---------|\n| `skills HAS \"billing\"` | Workers whose `skills` array contains \"billing\" |\n| `languages HAS \"es\"` | Spanish-speaking workers |\n| `department == \"support\"` | Workers in support department |\n| `experience > 5` | Workers with 5+ years experience |\n| `skills HAS \"billing\" AND languages HAS \"es\"` | Spanish-speaking billing agents |\n\n### Priority Routing\n\nTasks with higher priority are assigned first:\n\n```python\n# VIP customer — priority 10 (higher = first)\ntask = client.taskrouter.v1.workspaces(workspace_sid).tasks.create(\n attributes='{\"department\": \"billing\", \"priority\": 10, \"vip\": true}',\n workflow_sid=workflow.sid,\n priority=10\n)\n```\n\n### AI Agent Escalation\n\nWhen an AI agent (via TAC) escalates to a human, create a TaskRouter task with the AI's context:\n\n```python\n# From your escalation webhook handler\ndef handle_escalation(escalation_data):\n task = client.taskrouter.v1.workspaces(workspace_sid).tasks.create(\n attributes=json.dumps({\n \"department\": escalation_data[\"reason_code\"],\n \"conversation_id\": escalation_data[\"conversation_id\"],\n \"profile_id\": escalation_data[\"profile_id\"],\n \"ai_summary\": escalation_data[\"summary\"],\n \"priority\": 5\n }),\n workflow_sid=workflow.sid\n )\n```\n\nThe human agent receives the AI's conversation summary and customer profile.\n\n### Workflow with Timeout Escalation\n\nRoute to specialized queue first, then overflow to general:\n\n```python\nworkflow_config = {\n \"task_routing\": {\n \"filters\": [\n {\n \"filter_friendly_name\": \"Billing Specialist First\",\n \"expression\": \"department == 'billing'\",\n \"targets\": [\n {\"queue\": billing_queue.sid, \"timeout\": 60}, # Try billing queue for 60s\n {\"queue\": default_queue.sid, \"timeout\": 120} # Overflow to general\n ]\n }\n ],\n \"default_filter\": {\n \"queue\": default_queue.sid\n }\n }\n}\n```\n\n### Worker Activity Management\n\n```python\n# Set worker to available\nclient.taskrouter.v1.workspaces(workspace_sid) \\\n .workers(worker_sid) \\\n .update(activity_sid=available_activity_sid)\n\n# Get real-time worker statistics\nstats = client.taskrouter.v1.workspaces(workspace_sid) \\\n .workers \\\n .statistics() \\\n .fetch()\n\nprint(f\"Available: {stats.realtime['total_available_workers']}\")\n```\n\n---\n\n## Scale Guidance\n\n| Agents | Architecture | Notes |\n|--------|-------------|-------|\n| < 10 | Single workflow, one queue per skill | No Flex needed — agents use phone |\n| 10-50 | Multi-queue workflows, skills-based routing | Flex recommended for desktop |\n| 50+ | Multi-tier workflows, priority routing, real-time monitoring | Full Flex + supervisor tools |\n\n---\n\n## Gotchas\n\n### 1. Hyphens in Attribute Names Break Silently\n\n```python\n# WRONG — hyphens in attribute keys break workflow expressions\nworker = client.taskrouter.v1.workspaces(workspace_sid).workers.create(\n friendly_name=\"Alice\",\n attributes='{\"skill-level\": 5}' # hyphen breaks expression evaluation\n)\n\n# RIGHT — use underscores or camelCase\nworker = client.taskrouter.v1.workspaces(workspace_sid).workers.create(\n friendly_name=\"Alice\",\n attributes='{\"skill_level\": 5}'\n)\n```\n\nNo error — the expression silently fails to match.\n\n### 2. HAS Operator on Non-Array Attributes\n\n```python\n# WRONG — \"billing\" is a string, not an array. HAS silently matches nothing.\ntarget_workers = 'department HAS \"billing\"'\n\n# RIGHT — use == for string attributes\ntarget_workers = 'department == \"billing\"'\n\n# RIGHT — use HAS only for arrays\ntarget_workers = 'skills HAS \"billing\"' # skills: [\"billing\", \"technical\"]\n```\n\nTasks sit in queue forever with no error.\n\n### 3. Reservation Timeout Cascade\n\nWhen a reservation times out:\n1. Worker moves to the timeout Activity (often \"Offline\")\n2. Fewer workers available → other reservations also time out\n3. Positive feedback loop → entire queue backs up\n\n**Fix:** Set the timeout Activity to a short-duration state, not \"Offline\". Or implement a reservation timeout handler that keeps the worker available:\n\n```python\n@app.route(\"/taskrouter-events\", methods=[\"POST\"])\ndef taskrouter_event():\n event_type = request.form[\"EventType\"]\n if event_type == \"reservation.timeout\":\n worker_sid = request.form[\"WorkerSid\"]\n # Keep worker available instead of moving to offline\n client.taskrouter.v1.workspaces(workspace_sid) \\\n .workers(worker_sid) \\\n .update(activity_sid=available_activity_sid)\n return \"\", 200\n```\n\n### 4. Activity Available Flag\n\nUpdating an Activity's `available` flag returns 200 OK but may not change the value if workers are currently in that activity. Create new activities instead of modifying existing ones.\n\n---\n\n## CANNOT\n\n- **Hyphens in attribute names break expressions** — `skill-level` is treated as subtraction (`skill` minus `level`). Error 20001. Always use underscores: `skill_level`.\n- **`HAS` on non-array silently matches nothing** — `department HAS \"billing\"` on a string attribute is accepted at creation but never matches. Tasks sit in queue forever with no error.\n- **Expression validation is syntactic only** — Queue creation validates parse but NOT worker matching. Semantically wrong expressions create successfully with zero matching workers.\n- **Activity `available` flag is silently immutable** — Updating returns 200 OK but does not change the value. Must delete and recreate the Activity.\n- **`multiTaskEnabled` cannot be reverted to false** — Once enabled on a Workspace, cannot be disabled. One-way door.\n- **Reservation timeout moves worker to timeout Activity** — Worker automatically moved to Offline. Must manually set back. This cascades: fewer available workers → more timeouts → queue collapse. See Gotcha #3.\n- **Workflow target timeout auto-cancels tasks** — When all targets exhaust timeouts, task is canceled. Always include a `default_filter` as catch-all.\n- **Worker `friendlyName` is case-insensitive unique** — \"alice\" collides with \"Alice\".\n- **`workflowSid` is required for task creation** — API does not auto-select a default Workflow.\n- **Cannot update task status and attributes in same request** — Must be two separate API calls.\n- **Assignment callback must respond in 5 seconds** — If both primary and fallback URLs fail, reservation is canceled.\n- **Tasks auto-cancel after 1,000 rejections** — If a task cycles through 1,000 reservation rejections, it is automatically canceled.\n- **`page` query param not supported** — Use `PageToken` for pagination. `page` returns error 40153.\n- **Cannot use malformed JSON in worker attributes** — Silently breaks matching with no error\n- **Cannot use regex in workflow expressions** — Only supports ==, !=, <, >, HAS, IN, CONTAINS, AND, OR, NOT\n- **Cannot exceed 50,000 Workers per Workspace** — Hard limit\n- **Cannot exceed 250 Task Queues per Workspace** — Hard limit\n- **Cannot delay reservation callback response beyond 15 seconds** — Timeout results in reservation failure\n\n---\n\n## Next Steps\n\n- **Conference for transfers:** `twilio-conference-calls`\n- **Call recording:** `twilio-call-recordings`\n- **AI agent voice integration:** `twilio-voice-conversation-relay`\n- **Voice IVR before routing:** `twilio-voice-twiml`\n"
}SHA-256: badb4d56aae03e32fe74b7989634e7b3da121d25b1adf74389a545d3851fa070