Forward Calls and Fall Back to Voicemail
Configure a number to forward calls to one or more destinations, fall back to voicemail, or send straight to voicemail — including AI-agent handover.
A number's forward and voicemail routing blocks let you ring one or more destinations — sequentially or in parallel, first answer wins — with an optional voicemail fallback if nobody picks up, or send callers straight to voicemail with no ringing at all. Both are reusable config blocks: the same shapes also appear under an AI agent's handover, so an agent can route the caller it's handing off to a forward target or voicemail instead of (or ahead of) your handover_callback_url. This guide covers the dashboard controls and the underlying API for all four cases.
In the dashboard, open Numbers → <your number> → Routing. Under Inbound call handling, pick one mode:
voice_callback_url configured above (the pre-existing webhook flow).Only one mode is active at a time — saving clears the routing config for the other two (inbound_agent, forward, voicemail are independent fields under the hood; the dashboard just keeps exactly one of them populated). Picking Forward exposes a Destinations list, a Ring strategy toggle (Parallel/Sequential), a Stop ringing the rest as soon as one answers checkbox, Ring timeout, Caller ID, and a Send to voicemail if no answer checkbox that nests the same voicemail editor used by the standalone Voicemail mode (greeting text or a hosted greeting URL, max recording length, beep).
The same config is forward on PUT /v1/numbers/{id}/routing — see Bind an agent's routing for the full request/response reference. forward is tri-state exactly like inbound_agent: omit the key to leave the current target untouched, send null to clear it, or send a ForwardTarget object to validate and set it.
curl -s -X PUT "https://api.sautikit.com/v1/numbers/$NUMBER_ID/routing" \
-H "Authorization: Bearer $SAUTIKIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"forward": {
"destinations": ["+254700000001", "+254700000002"],
"sequential": false,
"first_answer_wins": true,
"ring_timeout": 20,
"caller_id": "+254712345678"
}
}' | jq .const response = await fetch(`https://api.sautikit.com/v1/numbers/${NUMBER_ID}/routing`, {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
forward: {
destinations: ["+254700000001", "+254700000002"],
sequential: false,
first_answer_wins: true,
ring_timeout: 20,
caller_id: "+254712345678",
},
}),
});
const { number, routing } = await response.json();
console.log(routing.forward);destinations accepts E.164 numbers (at least one non-blank entry is required). numbers/forward binding isn't yet covered by @sautikit/node — use fetch (or any HTTP client) against the REST endpoint as shown above.
sequential and first_answer_wins control how the destinations ring, and map directly to the same-named attributes on the underlying dial voice action (see the Dial reference) — a forward config is rendered straight to a Dial with no customer webhook round trip:
sequential: false (default) — ring every destination at once.sequential: true — ring destinations in order, advancing to the next on no-answer/busy within ring_timeout seconds.first_answer_wins: true — bridge whichever destination answers first and cancel the rest. This is the dashboard default.Ring the sales line and the on-call mobile simultaneously, connect whichever answers first:
{
"forward": {
"destinations": ["+254700000001", "+254700000002"],
"sequential": false,
"first_answer_wins": true,
"ring_timeout": 20
}
}Try the office line, then fall through to the mobile if it's not answered within the timeout:
{
"forward": {
"destinations": ["+254700000001", "+254700000002"],
"sequential": true,
"ring_timeout": 15
}
}ring_timeout, when set, must be between 5 and 120 seconds. Omit it to use the platform default (25s).
Nest a voicemail block under forward to capture a message if nobody answers within the ring window. Rendered as a Dial followed by the voicemail's greeting/Record/Hangup verbs — the PBX only reaches the voicemail tail if the Dial doesn't connect:
{
"forward": {
"destinations": ["+254700000001", "+254700000002"],
"sequential": false,
"first_answer_wins": true,
"ring_timeout": 20,
"voicemail": {
"greeting_text": "Sorry we missed your call. Leave a message after the tone.",
"max_length": 120
}
}
}Send a voicemail object as its own top-level field to send every caller straight to voicemail, with no Dial attempt. voicemail is tri-state the same way as forward: omit to keep, null to clear, an object to validate and set.
curl -s -X PUT "https://api.sautikit.com/v1/numbers/$NUMBER_ID/routing" \
-H "Authorization: Bearer $SAUTIKIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"voicemail": {
"greeting_text": "Sorry we missed your call. Leave a message after the tone.",
"max_length": 120,
"beep": true
}
}' | jq .greeting_text (text-to-speech) and greeting_play_url (a hosted audio file) are mutually exclusive — set at most one. max_length, when set, must be between 5 and 600 seconds. beep is accepted and stored today but not yet rendered on the live call — the PBX beep mechanism for Record is a follow-up.
An AI agent's handover can target the same two blocks instead of (or ahead of) handover_callback_url — see Handle the CallHandover callback for the callback itself. Set handover_forward or handover_voicemail alongside the rest of inbound_agent:
curl -s -X PUT "https://api.sautikit.com/v1/numbers/$NUMBER_ID/routing" \
-H "Authorization: Bearer $SAUTIKIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inbound_agent": {
"enabled": true,
"agent_id": "9d2b1f53-8c0e-4f1d-9a6b-5d3a8c47e9f0",
"mode": "always",
"handover_callback_url": "https://example.com/voice",
"handover_forward": {
"destinations": ["+254700000001"],
"ring_timeout": 20
}
}
}' | jq .In the dashboard, this is the On handover sub-selector under the AI Agent panel — a second, independent choice of Callback URL / Forward / Voicemail, using the same Forward/Voicemail editors as Step 1.
Two independent precedence chains, both evaluated top to bottom — the first populated field wins:
inbound_agent (only if it actually answers this edge) → forward → voicemail → voice_callback_url / legacy voice-actions.handover_forward → handover_voicemail → handover_callback_url.The dashboard's routing-mode radio only ever keeps one of inbound_agent/forward/voicemail populated (and, independently, one of handover_callback_url/handover_forward/handover_voicemail), but the API itself does not enforce that — you can set more than one via direct API calls, and the precedence above decides which one actually answers.
The forward object failed validation. Check details[0]: destinations_required (no non-blank destination), invalid_ring_timeout (must be 5–120s), invalid_caller_id (not E.164), voicemail_greeting_conflict or invalid_greeting_url / invalid_max_length (from the nested voicemail, same codes as below).
The voicemail object failed validation. Check details[0]: voicemail_greeting_conflict (both greeting_text and greeting_play_url set), invalid_greeting_url (not a valid http(s) URL), invalid_max_length (must be 5–600s).
Confirm the number's routing mode is actually Forward — the dashboard's mode selector clears the others on save, so a stray inbound_agent or a plain voice_callback_url won't be overridden by a forward block you set separately via the API unless inbound_agent is disabled/unset. Also check destinations isn't empty.
PUT /v1/numbers/{id}/routing reference, including forward/voicemail/inbound_agentnumbers/sequential/first_answer_wins fields a forward config renders to