API Reference
Numbers

Update a tenant number's routing config

PUT
/v1/numbers/{id}/routing

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.
  • modealways (answer every inbound call) or after_hours (answer only outside the schedule windows; see InboundAgentConfig).
  • handover_callback_url — REQUIRED while enabled unless you set handover_forward or handover_voicemail (either one answers the handover itself); defaults to this number's voice_callback_url when omitted. Sautikit POSTs the CallHandover event here when the agent transfers the call (e.g. to a human); you respond with voice actions (dial, say, …). See the CallHandoverCallback schema.

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 to voice_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 to voice_callback_url when set, else auto-bridge the requested destination.
  • connect — auto-bridge the requested destination directly with no webhook round trip, IGNORING voice_callback_url on 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
AuthorizationBearer <token>

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

id*string
Formatuuid

Header Parameters

Idempotency-Key?string
Lengthlength <= 64

Request 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"    ]  }}