← Twilio Developer KitCONTENT HISTORY

Update to Twilio Developer Kit

Snapshot Sep 30, 2026 · 22:50 UTC · version 0.2.2

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full 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