Update a tenant number's routing config
Patches voice_callback_url, events_url, and/or inbound_agent
on a number's routing config. voice_callback_url/events_url
accept http or https; empty string clears either field; omitted
fields are left untouched. The Idempotency-Key header is
honoured for replay safety.
Assign an AI agent to this number
Send an inbound_agent object to make one of your AI agents answer
inbound calls to this number instead of POSTing to
voice_callback_url:
{ "inbound_agent": { "enabled": true, "agent_id": "9d2b1f53-8c0e-4f1d-9a6b-5d3a8c47e9f0", "mode": "always", "handover_callback_url": "https://example.com/sautikit/voice" }}agent_id— an agent in this workspace that has at least one PUBLISHED REVISION. An agent you are currently editing still qualifies: its live published revision keeps answering while the new draft is unpublished.mode—always(answer every inbound call) orafter_hours(answer only outside theschedulewindows; seeInboundAgentConfig).handover_callback_url— REQUIRED whileenabledunless you sethandover_forwardorhandover_voicemail(either one answers the handover itself); defaults to this number'svoice_callback_urlwhen omitted. Sautikit POSTs theCallHandoverevent here when the agent transfers the call (e.g. to a human); you respond with voice actions (dial,say, …). See theCallHandoverCallbackschema.
To STOP an AI agent answering, send inbound_agent: null (clears the
binding). To pause without clearing, send the object with
enabled: false.
inbound_agent uses TRI-STATE semantics distinct from the two URL
fields above (which only distinguish set-non-empty vs
clear-via-empty-string):
- omitted from the request body — leave the current binding untouched.
- JSON
null— clear the binding (no AI agent answers this number; routing falls through tovoice_callback_url/ legacy voice-actions). - a JSON object — validate (see
InboundAgentConfig) and set.
Forward a number
Send a forward object to ring one or more destinations when this
number is called directly (the same shape also appears under
InboundAgentConfig.handover_forward for AI-agent handover):
{ "forward": { "destinations": ["+254700000001", "+254700000002"], "sequential": false, "first_answer_wins": true, "ring_timeout": 20 }}forward uses the SAME tri-state semantics as inbound_agent:
omit to keep the current target, send null to clear it, or send
a ForwardTarget object to validate and set it
(numbers.invalid_forward on failure).
Queue the call
Send a queue object to hold callers in an ordered line, announce
their position, and offer them to a pool one caller at a time:
{ "queue": { "enabled": true, "greeting_text": "All of our team are busy. Please hold.", "agents": { "destinations": ["1001", "1002"], "first_answer_wins": true, "ring_timeout": 20 }, "announce_every_seconds": 90, "on_timeout": "voicemail", "voicemail": { "greeting_text": "Leave a message." } }}A four-digit destination is an internal extension and rings the
device registered to it. queue uses the same tri-state semantics
as inbound_agent, and OUTRANKS forward and voicemail when
enabled — so a number may keep a forward configured as the fallback
it reverts to when the queue is switched off.
Hold time is bounded by the PBX rather than by this config: the box
allows 50 actions per call, so a caller can hold for roughly ten
minutes at the default cadence. The queue takes its on_timeout
exit before that runs out, so the caller is offered voicemail
rather than cut off.
Send to voicemail
Send a voicemail object to configure a reusable voicemail
fallback (a text-to-speech greeting or a pre-recorded greeting
URL, plus recording limits):
{ "voicemail": { "greeting_text": "Sorry we missed your call. Leave a message after the tone.", "max_length": 120, "beep": true }}voicemail is also tri-state: omit to keep, null to clear, an
object to validate and set (numbers.invalid_voicemail on
failure). beep is accepted and stored but not yet rendered on
the live call (reserved for a future release).
Outbound routing mode
Set outbound_mode to control how calls PLACED FROM this number
route on the outbound edge (it has no effect on inbound):
server(default; also the value when unset) — forward tovoice_callback_urlwhen set, else auto-bridge the requested destination.connect— auto-bridge the requested destination directly with no webhook round trip, IGNORINGvoice_callback_urlon the outbound leg (that URL still serves the inbound "your server" mode).
outbound_mode is a string field: omit to keep the current value,
send an empty string to reset to server, or send connect
(numbers.invalid_outbound_mode on any other value).
Authorization
bearerAuth Long-lived ES256 JWT minted from the dashboard (https://app.sautikit.com/developers/api-keys). Signed by the
platform keyring. Carries workspace_id and scopes claims;
revoked via the platform deny-list.
In: header
Path Parameters
uuidHeader Parameters
length <= 64Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
curl -X PUT "https://example.com/v1/numbers/497f6eca-6276-4993-bfeb-53cbbbba6f08/routing" \ -H "Content-Type: application/json" \ -d '{ "voice_callback_url": "https://example.com/sautikit/voice", "events_url": "https://example.com/sautikit/events", "inbound_agent": { "enabled": true, "agent_id": "9d2b1f53-8c0e-4f1d-9a6b-5d3a8c47e9f0", "mode": "after_hours", "handover_callback_url": "https://example.com/sautikit/voice", "schedule": { "timezone": "Africa/Nairobi", "hours": [ { "days": [ 1, 2, 3, 4, 5 ], "start": "08:00", "end": "17:30" } ] } } }'{ "number": { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9", "e164": "string", "friendly_name": "string", "status": "active", "assigned_at": "2019-08-24T14:15:22Z" }, "routing": { "voice_callback_url": "string", "events_url": "string", "inbound_agent": { "enabled": true, "agent_id": "9d2b1f53-8c0e-4f1d-9a6b-5d3a8c47e9f0", "mode": "after_hours", "schedule": { "timezone": "Africa/Nairobi", "hours": [ { "days": [ 1, 2, 3, 4, 5 ], "start": "08:00", "end": "17:30" } ] }, "handover_callback_url": "https://hooks.example.com/handover" }, "forward": { "destinations": [ "+254700000001", "+254700000002" ], "sequential": false, "first_answer_wins": true, "ring_timeout": 20, "caller_id": "+254712345678" }, "queue": { "enabled": true, "agents": { "destinations": [ "+254700000001", "+254700000002" ], "sequential": false, "first_answer_wins": true, "ring_timeout": 20, "caller_id": "+254712345678" }, "greeting_text": "string", "greeting_play_url": "string", "greeting_voice": "man", "hold_play_url": "string", "hold_duration_seconds": 0, "announce_position": true, "announce_every_seconds": 15, "tick_seconds": 15, "max_wait_seconds": 0, "max_depth": 1, "on_timeout": "hangup", "voicemail": { "greeting_text": "Sorry we missed your call. Leave a message after the tone.", "max_length": 120, "beep": true }, "overflow_forward": { "destinations": [ "+254700000001", "+254700000002" ], "sequential": false, "first_answer_wins": true, "ring_timeout": 20, "caller_id": "+254712345678" } }, "voicemail": { "greeting_text": "Sorry we missed your call. Leave a message after the tone.", "max_length": 120, "beep": true }, "outbound_mode": "server" }}{ "error": { "code": "numbers.invalid_inbound_agent", "message": "inbound_agent config is invalid: schedule is required when mode is after_hours", "request_id": "req-a1b2c3d4", "details": [ "schedule_required" ] }}{ "error": { "code": "validation.bad_request", "message": "string", "request_id": "string", "details": [ "string" ] }}Release a tenant number DELETE
Marks the number released, returns the DID to the available pool, and emits a `number.released` event. Idempotent — re-releasing a released number returns 204 with no event.
Per-country outbound call rates for a tenant number GET
Returns the workspace's currently-effective per-country outbound rate card for the given number, sourced from the provider series rates resolved against the number's origin country and the workspace currency.