Call Routing
Decide who answers each phone line: the voice AI, your team in the help desk, or voicemail, with daily time slots in the business time zone. Covers how team calls ring agents one by one, voicemail greetings and recordings, missed calls, and routers: Python route_call(context) that can end a call before it is answered, with its return values, fail-open behaviour, tests, execution history and versions.
When a call reaches one of your phone lines, Octocom decides who answers it in this order:
- The line's router, if one is attached, can end the call before anything else happens.
- The active time slot, if the line has one for the current time, decides who answers.
- Otherwise the line's own setting decides.
Who answers
| Dashboard option | Value | What happens |
|---|---|---|
| AI voice agent | ai | The voice AI answers, with the line's own settings. |
| Human agents | your_team | Your team answers in the Octocom help desk. See When your team answers. |
| Voicemail | voicemail | The caller hears a greeting and can leave a message. See Voicemail. |
A line or time slot answered by the AI has its own copy of the voice AI settings. Switching it away from the AI and back keeps them.
In the dashboard
Open Settings -> Voice & SMS -> Phone numbers and click Edit routing on a number. Choose who answers, add a greeting if needed, and click Save routing. Choosing AI voice agent copies the settings of the voice agent you pick.
The dashboard changes who answers an existing route. Time slots are created and changed through Copilot or MCP. Demo numbers and SIP lines are routed with their voice agent instead.
With Copilot or MCP
Use update_phone_line with answeredBy, and create_phone_line_time_slot, update_phone_line_time_slot and delete_phone_line_time_slot for schedules. list_phone_lines shows each line's routing, its time slots, its time zone and how to call it.
Time slots
A time slot gives a line a different answer for part of each day, for example the AI at night and your team during business hours.
- Slots repeat every day and use the business's time zone.
list_phone_linesshows it astimeZone. - Times are 24-hour
HH:MM. Both the start and the end minute belong to the slot. - A slot can run past midnight: a start later than the end, such as
22:00-06:00, wraps. - Slots can't overlap, except that one may start in the minute another ends. In that shared minute, the slot that starts earlier applies.
- The start and end can't be the same.
- Outside every slot, the line's own setting applies. If the line has no setting of its own, calls outside its slots aren't answered.
Each slot has its own greetings and, when answered by the AI, its own voice AI settings.
When your team answers
A call for your team creates a conversation in the help desk, already handed off to your team, and assigns it like any other conversation: by your assignment rules (the channel is Phone Call) and your agents' availability. Then:
- The assigned agent's browser rings for about 30 seconds. See Phone calls in the help desk.
- If they decline or don't answer, the next available agent is tried, one at a time. Agents who declined aren't tried again for that call.
- If nobody is left, the caller hears the line's voicemail greeting and can leave a message. Without a voicemail greeting, the caller hears "There are no available agents at this time. You will be called back once an agent becomes available." and the call ends.
If no agent is available when the call arrives, the caller goes to the voicemail greeting straight away; without one, the call ends.
Set a voicemail greeting for your team (yourTeamVoicemailGreeting, or Voicemail greeting (optional) in the dashboard) so callers can always leave a message.
A call nobody answered stays in the help desk as an open conversation with a "Call missed - no agents available" event, and the voicemail if the caller left one. An agent can call the customer back from that conversation.
The voice AI can also send a call to your team mid-call with the your_team transfer. The call then rings agents in the same way, and the AI's transcript is in the same conversation.
Voicemail
The caller hears the voicemail greeting, then can leave a message of up to 60 seconds. After the message, they hear a short goodbye, such as "Thank you for your message. Goodbye." in English, in the line's language.
The greetings are spoken in the line's prompt language (language on the line): English, German, French, Spanish, Italian, Dutch, Swedish, Norwegian or Danish. English is the default. This is separate from the voice AI's language.
The recording and its transcript appear in the help desk conversation, with a "Voicemail received" event. The conversation stays open for your team.
| Greeting | When it's used |
|---|---|
voicemailGreeting | Required for a line or slot answered by Voicemail. Shown as Voicemail greeting in the dashboard. |
yourTeamVoicemailGreeting | Optional for Human agents, used when nobody answers. Shown as Voicemail greeting (optional) in the dashboard. |
Each line and each time slot has its own greetings, up to 4,000 characters each.
Routers
A router is Python that runs when a call reaches a phone line, before time slots and who answers. Its route_call(context) function lets the call continue, or ends it. Use it to block known spam numbers, check a caller against your CRM, or close a number on holidays.
The caller hears ringing while it runs. Changes take effect on the next call, without deploying code.
Create and attach a router
Open Settings -> Voice & SMS -> Phone line routers, create a router, and test it with a sample call. Then attach it to a number from Settings -> Voice & SMS -> Phone numbers. Through MCP or Copilot, use create_phone_line_router, then update_phone_line with its routerId.
Routers belong to the organization. One router can serve many phone lines, also across businesses, and branch on the line that was called. A phone line has at most one router.
BLOCKED = {"+15550000001", "+15550000002"}
def route_call(context):
call = context["call"]
if call["caller_phone_number"] in BLOCKED:
return {"action": "hangup"}
return NoneWhat the router returns
| Return value | What happens |
|---|---|
None or {"action": "continue"} | The call continues with the line's own routing: its time slots, then who answers. |
{"action": "hangup"} | The call is rejected without being answered. Twilio doesn't charge for it. |
{"action": "hangup", "voice_message": "We're closed."} | The line answers, says the message in the line's language, and hangs up. The call is billed. |
A rejected call creates no conversation. Its only record is the router's execution history.
Call context
context["call"] describes the ringing call. context["business"] holds the name and slug of the called line's business. The usual Python helpers (get_secret, organization_store_*, ...) are globals, as in custom actions. Helpers that work on a conversation do nothing, because there is no conversation yet.
| Field | Value |
|---|---|
context["call"]["caller_phone_number"] | Who is calling, usually in E.164 format |
context["call"]["twilio_call_sid"] | Twilio's ID of the call. None in a test |
context["call"]["is_test"] | True when the call is a test |
context["call"]["phone_line"] | The called line: id, name, address, business_id, language |
When a router fails, the call continues
A router never drops a call by accident. The call continues with the line's own routing when the router:
- raises an error,
- returns anything other than the values above,
- takes longer than 5 seconds, or
- can't run yet because the organization's Python sandbox is starting up.
The sandbox starts the first time the organization runs Python, and again after nothing ran for a while. Starting takes 10 to 15 seconds, longer than a caller can wait, so the router is skipped for that call while the sandbox starts in the background. A test starts it too, so the first real call after testing finds it ready.
Each of these shows up in the execution history as outcome fail_open, with the reason error, invalid_result, timeout or sandbox_starting.
Test a router
Test a router with a sample call: a caller number and the phone line it calls. The test runs the code exactly like a real call, with the same context and deadline, and shows the outcome, the returned value, printed output, any error with its traceback, and how long it took. You can test unsaved code before saving it.
If the sandbox had to start first, the test waits for it and says so. A real call at that moment would have skipped the router.
Tests run real code: requests and writes your code makes are real.
Execution history
Every call through a router and every test is recorded for 90 days: the caller, the phone line, the outcome, the returned value, the duration, and any error with its output. Read it with list_phone_line_router_executions, or the last few runs with get_phone_line_router.
Versions and archiving
Every change is saved as a version. Restore one with restore_phone_line_router_version. A router can't be archived while a phone line uses it; detach it first. Restoring a version also restores an archived router.