Agent Number

Voice

Place and receive voice calls on the same number that handles SMS. One subscription, one phone, both channels.

How voice works on Agent Number

Voice uses two WebSocket channels with different jobs. Both stay open during a live call.

The presence WS is a long-lived listener; the stream WS is per-call. They can coexist — presence keeps listening for additional incoming calls while a stream WS carries the current one.

Inbound call flow

  1. Agent opens presence WS at wss://agentnumber.really.com/voice/presence/<phone> with Authorization: Bearer <token>. Server auto-answers any incoming call.
  2. External caller dials your number.
  3. Server pushes {event: "ringing", call_id, caller, stream_url} on the presence WS.
  4. Agent opens stream WS at the provided stream_url.
  5. Agent receives caller audio as binary frames, sends agent audio back the same way.
  6. On hangup (either side), stream WS closes. Presence WS stays open for the next call.

Outbound call flow

  1. Agent calls POST/voice/dial with phone_number (one of the subscription's owned numbers, the call's source) and to (destination).
  2. Response: {call_id, stream_url, direction, caller, phone_number}. The target phone is now ringing.
  3. Agent opens stream WS at stream_url and waits for connect.
  4. On answer, audio flows both directions until hangup.

Place a call

curl -X POST https://agentnumber.really.com/voice/dial \
  -H "authorization: Bearer <token>" \
  -H "content-type: application/json" \
  -d '{"phone_number": "+15555550123", "to": "+15551234567"}'
# → {"call_id": "...", "stream_url": "wss://...", "direction": "outbound", "caller": "+15551234567", "phone_number": "+15555550123"}

Open the stream WS

Use any WS client. Audio is binary frames; the server handles cellular bridging. Auth on the stream WS uses the same bearer.

Endpoints

EndpointAuthPurpose
POST/voice/dialbearerPlace outbound call. Body: {phone_number, to}. Returns {call_id, stream_url, direction, caller, phone_number}.
POST/voice/hangupbearerEnd an active call. Body: {call_id}. Returns {call_id, device_id, status: "hangup_sent"}.
GET/voice/presence/<phone> WSbearerOpen to receive incoming-call signals.
GET/voice/stream/<call_id> WSbearerAudio stream for a specific call.

An internal/debug-only v1 status probe exists for fleet operations, but it is not part of the public API and is intentionally not advertised pending the post-launch security review.

Practical notes

Common errors

StatusEndpointErrorMeaning
401allmissing bearer token / invalid bearer tokenMissing or invalid bearer — see Auth
400/voice/dial, /voice/hangupinvalid_requestBody malformed or missing required fields
403/voice/dialnot_ownerBearer doesn't own the specified phone_number
404/voice/dialphone_number_not_foundphone_number is not on an active subscription
404/voice/hangup, stream WScall_not_foundcall_id doesn't exist or isn't owned by your subscription
409/voice/dialphone_busyNumber already on a call (single-active-call constraint per number)
409/voice/dialsubscription_not_boundSubscription has no (device_id, sim_number) slot binding
502/voice/dial, /voice/hangupmac_unreachable / mac_dial_failed / phone_node_errorHardware adapter returned non-2xx or was unreachable