API Reference
SIP Trunk

Disconnect the workspace's SIP trunk

DELETE
/v1/sip-trunk

Refused while any BYO numbers still ride the trunk — release them first.

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

Response Body

application/json

application/json

curl -X DELETE "https://example.com/v1/sip-trunk"
Empty
{  "error": {    "code": "validation.bad_request",    "message": "string",    "request_id": "string",    "details": [      "string"    ],    "resolution": "string",    "reason": "invalid_characters",    "suggested_e164": "+254727524723"  }}
{  "error": {    "code": "validation.bad_request",    "message": "string",    "request_id": "string",    "details": [      "string"    ],    "resolution": "string",    "reason": "invalid_characters",    "suggested_e164": "+254727524723"  }}

Update the workspace's SIP trunk PUT

Updates the workspace's single trunk — there is no trunk ID in the path because a workspace has only ever had, at most, one. It also creates one when none exists, for clients written before `POST` existed; `POST` is the clearer way to connect a new trunk, because it refuses to overwrite one that is already there. Refused with `workspace.locked_to_platform_numbers` on a workspace that already buys numbers from Sautikit. For a `registration` trunk, the password is pushed to Sautikit's box and a REGISTER is attempted synchronously, inside this same request (up to ~20s). The response's `status` reflects the outcome immediately: `active` on success, `failed` (with `last_error`) if the provider rejected the credentials or was unreachable — the workspace stays unlocked either way until it actually succeeds. Omit `password` on a later `PUT` to keep the password already on file (e.g. when only changing a label); a password rotation re-signals every active BYO number on the trunk automatically. For an `ip_allowlist` (signaling) trunk, this only submits the trunk for review — it lands at `pending_approval` and does not go live, or lock the workspace, until a person on Sautikit's operations team approves it and installs both the signalling and media IPs on the box.

Upload an audio file (mp3/wav) to play back on a call POST

Streams the uploaded file to Sautikit's object storage and returns a signed `storage.sautikit.com` playback URL — the same branded-CDN mechanism `GET /v1/calls/{call_id}/recording` uses for captured call recordings. This is how you obtain a hosted URL for any "play a recording" setting; there is no other supported way to hand us audio. The returned `url` is what you pass on `PUT /v1/numbers/{id}/routing` to every field that plays a recording: - `voicemail.greeting_play_url` — the voicemail greeting. - `queue.greeting_play_url` — spoken once, as the caller joins. - `queue.hold_play_url` — the hold loop. Send `queue.hold_duration_seconds` alongside it: no verb can cut a `<Play>` short, so the file's real length is what sets the queue's cadence, and a wrong value there turns a short clip into a rapid-fire callback loop. - the same `voicemail` / `queue` blocks nested under `forward.voicemail`, `queue.voicemail` and `inbound_agent`. Accepts `multipart/form-data` with a single `file` field. Only `audio/mpeg` (mp3) and `audio/wav` (`audio/wav`, `audio/wave`, `audio/x-wav`) are accepted — anything else is rejected with `uploads.unsupported_media`. Max size 10 MiB (`uploads.too_large` above that). API-key callers need the `numbers.claim` scope; viewers are rejected. The URL is presigned for 7 days (SigV4's maximum), but you do NOT need to re-upload to keep a saved greeting working: a stored `*_play_url` is re-signed automatically each time the call is rendered, so it keeps playing indefinitely. The 7-day window only bounds the returned link itself — treat it as expiring if you hand it to a browser or store it somewhere outside a number's routing config.