VOICE / transfer-a-sip-call-with-refer

Voice

Transfer a SIP call with refer

Hand an active SIP Speech to Speech caller to another PSTN or SIP destination when the agent should escalate to a human queue, a specialist URI, or a Twilio SIP Domain. Official Voice REST documents POST https://api.x.ai/v1/realtime/calls/{call_id}/refer with a JSON body containing target_uri. The SIP Phone Calls FAQ states the request blocks until the transfer resolves, that HTTP status reports whether the destination answered, and that there is no separate WebSocket transfer event — read the HTTP response.

What you need

A live SIP session whose call_id came from the realtime.call.incoming webhook (same id you used on wss://api.x.ai/v1/realtime?call_id=…), an inference API key, and a destination URI: tel:+E.164 for PSTN or sip:user@host (including URI parameters for Twilio Programmable Voice SIP Domains). Neighboring jobs include Handle SIP phone calls with Speech to Speech for joining the call, Resume a SIP conversation across calls when you continue history later instead of transferring now, and List registered SIP phone numbers for inventory. More voice jobs live on the Voice hub.

Send the refer

While the realtime WebSocket is still open for that call_id, POST the transfer. The caller hears dialtone while REFER is pending.

export XAI_API_KEY="your_api_key"
CALL_ID="00000000-0000-0000-0000-000000000000"

curl -i -X POST "https://api.x.ai/v1/realtime/calls/${CALL_ID}/refer" \
  -H "Authorization: Bearer ${XAI_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"target_uri": "sip:agent@example.com"}'
import os

import requests

call_id = "00000000-0000-0000-0000-000000000000"
response = requests.post(
    f"https://api.x.ai/v1/realtime/calls/{call_id}/refer",
    headers={
        "Authorization": f"Bearer {os.environ['XAI_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={"target_uri": "tel:+14155550199"},
)
print(response.status_code, response.text)

A 200 with empty JSON {} means the transfer completed: REFER succeeded and the destination answered — not merely that REFER was accepted or that the far end started ringing. Use tel:+… for PSTN and sip:… for SIP routing; Twilio SIP Domain transfers can include URI parameters on the sip: value. Custom SIP headers are not sent on the REFER.

Handle failure without dropping the caller

Docs say after a failed transfer (502 with a downstream SIP reason, 504 timeout, or other errors) the same realtime session stays connected. The failed transfer is a no-op on the SpaceXAI side: the caller remains, and the agent can keep talking. Read the HTTP body (for example {"error":"transfer rejected by downstream: SIP 403 Forbidden"}) and either explain the failure on this session or resume later with conversation resumption. Other statuses include 400 when target_uri is not tel: or sip:, and 404 when no SIP participant is on the call.

Pitfalls

Waiting for a WebSocket “transfer complete” event never fires — outcome is only on the HTTP refer response. Treating 200 as “REFER accepted” without confirming answer undercounts failed destinations that ring then reject. Ending the WebSocket before refer finishes can strand the caller on dialtone. Using hangup (POST …/hangup) when you meant transfer drops the PSTN leg instead of bridging to a human.