---
title: "Originate an outbound leg"
method: POST
path: "/legs"
tags: ["Legs"]
---

# Originate an outbound leg

`POST /legs`

Originate a new outbound leg. The `type` field selects the transport: `sip` originates a SIP INVITE; `whatsapp` originates a WhatsApp call through Meta; `websocket` dials a remote WebSocket endpoint (audio is PCM in either binary or `json_base64` framing, with bidirectional text and caller-supplied X-/P- headers).

## Request body

- CreateLegRequest
  - `type` 'sip' | 'whatsapp' | 'websocket' | 'livekit_room', required — Leg type
  - `to` string — Destination. For sip legs, a SIP URI (e.g. "sip:alice@example.com"). For whatsapp legs, an E.164 phone number (with or without '+').
  - `uri` string — Deprecated alias for `to` (sip legs only). Prefer `to`.
  - `from` string — Caller ID. A bare user-part (e.g. "+15551234567", "alice") sets the user of the SIP From header. A full SIP URI (e.g. "sip:alice@pbx.example.com") sets both the user and the host; otherwise the host comes from the matched trunk's AOR realm, falling back to SIP_DOMAIN.
  - `privacy` string — SIP Privacy header value (e.g. "id", "none")
  - `ring_timeout` integer — Seconds to wait for answer; 0 = no timeout
  - `max_duration` integer — Maximum call duration in seconds after connect. Automatically hung up when reached. 0 or omitted = no limit.
  - `codecs` string[] — Codec preference order (sip legs only)
  - `headers` object — Custom headers to include in the outbound INVITE (sip/whatsapp) or the WebSocket upgrade request (websocket)
  - `room_id` string — Room ID to auto-add the leg to once media is ready (early_media or connected). If the room does not exist, it is automatically created.
  - `auth` SIPAuth
    - `username` string — Digest auth username. Optional for whatsapp legs (defaults to `from` with '+' stripped, per Meta's spec).
    - `password` string, required — Digest auth password.
  - `webhook_url` string, uri — Route all events for this leg exclusively to this URL instead of global webhooks.
  - `webhook_secret` string — HMAC-SHA256 signing secret for the per-leg webhook.
  - `amd` AMDParams
    - `initial_silence_timeout` integer — Max milliseconds of silence before declaring no_speech
    - `greeting_duration` integer — Speech duration threshold (ms) above which answerer is classified as machine
    - `after_greeting_silence` integer — Silence duration (ms) after initial speech to declare human
    - `total_analysis_time` integer — Max analysis window in milliseconds. A threshold longer than this window suppresses that verdict; a window shorter than all of initial_silence_timeout, greeting_duration and after_greeting_silence is rejected, since the call could only end not_sure.
    - `minimum_word_length` integer — Minimum speech burst duration (ms) to count as a word
    - `beep_timeout` integer — Max time (ms) to wait for the voicemail beep after machine detection. 0 or omitted = disabled.
  - `accept_dtmf` boolean — If false, this leg will not receive DTMF digits broadcast from other legs in the same room. Defaults to true.
  - `app_id` string — Application identifier. Carried through to all events for this leg. Use to filter the WebSocket event stream by app.
  - `speech_detection` boolean — If true, emit speaking.started and speaking.stopped events for this leg. If false, suppress them. Omit to use the server default (SPEECH_DETECTION_ENABLED env var, default false).
  - `rtt` boolean — For sip legs: offer Real-Time Text (ITU-T T.140 over RTP per RFC 4103) alongside audio. For websocket legs: enable the bidirectional text-message channel. Default: false.
  - `streams` CreateLegStream[] — SIP outbound only. Extra m=audio sections to offer alongside the call's primary bidirectional audio, so a multi-stream call is established by the first INVITE instead of a follow-up re-INVITE. Each entry binds its own RTP port and may be mixed into its own room. To add a stream to a call that is already up, use POST /v1/legs/{id}/streams instead.
    - `direction` 'sendrecv' | 'sendonly' | 'recvonly' | 'inactive' — Media direction for this stream, from this server's point of view. Defaults to sendrecv.
    - `lang` string — BCP 47 language tag advertised as a=lang (RFC 8866), e.g. "es-ES" for a Spanish translation feed.
    - `content` 'main' | 'alt' | 'speaker' | 'slides' | 'sl' — Value advertised as a=content (RFC 4796). Use "alt" for an alternative feed such as a translation.
    - `label` string — Value advertised as a=label (RFC 4574), for correlating the stream with external metadata.
    - `room_id` string — Room to mix this stream into once the call connects. May differ from the leg's own room_id, which governs the primary stream.
    - `role` string — Routing role for this stream inside its room.
  - `url` string, uri — WebSocket target URL (ws:// or wss://) for outbound websocket legs. Required when type=websocket.
  - `sample_rate` integer — PCM sample rate for websocket legs. The room's mixer automatically resamples between this and the room rate.
  - `wire_format` 'binary' | 'json_base64' — Audio framing for websocket legs. `binary` ships raw PCM as WebSocket binary frames; `json_base64` wraps PCM as `{"type":"audio","audio":"<base64>"}` text frames (browser-friendly).
  - `sample_format` 's16le' — On-the-wire PCM sample encoding for websocket legs. v1 only supports `s16le`.
  - `livekit` LiveKitParams
    - `url` string, uri — LiveKit server endpoint (wss://...). Overrides LIVEKIT_URL.
    - `token` string — Pre-signed LiveKit JWT. Mutually exclusive with `room`/`identity` (mint mode); if both are present the token wins.
    - `room` string — LiveKit room name. Required when minting (i.e. `token` is empty AND LIVEKIT_TOKEN_SIGNING_ENABLED=true).
    - `identity` string — LiveKit participant identity. Required when minting.
    - `participant_name` string — Display name for the participant; surfaces in LK Room UIs.
    - `permissions` LiveKitPermissions
      - `can_publish` boolean — Allow publishing tracks. Default true.
      - `can_subscribe` boolean — Allow subscribing to remote tracks. Default true.
      - `can_publish_data` boolean — Allow publishing data channel messages. Default false (audio bridge does not use data).
      - `room_admin` boolean — Grant admin actions on the room (e.g., server-side MuteTrack of remote participants). Default false.
    - `token_ttl` string — Go duration string (e.g. "30m", "6h"). Used only when minting. Defaults to LIVEKIT_DEFAULT_TOKEN_TTL (6h).
    - `opus_bitrate` integer — Override LIVEKIT_OPUS_BITRATE for this leg. 6000..510000.

## Response `201`

Leg created

- Leg
  - `instance_id` string — Instance identifier
  - `id` string, required — Unique leg identifier (UUID)
  - `type` 'sip_inbound' | 'sip_outbound' | 'webrtc' | 'whatsapp_in' | 'whatsapp_out' | 'websocket_in' | 'websocket_out' | 'moq_in' | 'livekit_publish' | 'livekit_participant', required — Leg type
  - `state` 'ringing' | 'early_media' | 'connected' | 'held' | 'hung_up', required — Leg state
  - `room_id` string — Room ID if the leg is in a room, empty otherwise
  - `muted` boolean, required — Whether the leg is muted (cannot be heard by others)
  - `deaf` boolean, required — Whether the leg is deaf (cannot hear others)
  - `accept_dtmf` boolean, required — Whether the leg receives DTMF digits broadcast from other legs in the same room. Defaults to true.
  - `held` boolean, required — Whether the call is on hold (SIP legs only)
  - `role` string — Routing role used by the room's audio routing matrix (e.g. "customer", "agent", "supervisor"). Empty string means unroled (full mesh).
  - `app_id` string — Application identifier for event stream filtering.
  - `sip_headers` object — Deprecated: X-* headers from the inbound INVITE. Only present on sip_inbound legs. Use `headers` for new code; it carries the same map plus surfaces handshake headers for websocket legs.
  - `headers` object — Custom protocol headers exposed by the leg's transport — X-/P- headers from a SIP INVITE, the WebSocket upgrade request, or supplied at outbound dial time.

## Other responses

- `400` — Invalid JSON, bad URI/URL, unknown codec, or unsupported type

---

[API](https://skmtc.net/voiceblender/apis/voiceblender-api.md) · [All operations](https://skmtc.net/voiceblender/apis/voiceblender-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/voiceblender/voiceblender-api/revisions/ad30827d3b57/schema)
