All docs

Voice API Reference

Endpoints for voice numbers, calls, intents, and billing.

All voice endpoints sit under https://api.senderz.com/v1 and use the same Authorization: Bearer API key as the messaging endpoints. Every response carries a code field on errors so you can branch without parsing prose.

Voice numbers

POST /v1/voice/numbers

Buy a number and attach it to your voice connection. Returns 503 NOT_CONFIGURED if voice is not provisioned for your account, 404 NOT_AVAILABLE if no numbers in the requested area code, 502 TELNYX_ERROR if the carrier rejects the order.

curl -X POST https://api.senderz.com/v1/voice/numbers \
  -H "Authorization: Bearer tf_live_YOUR_KEY" \
  -d '{"country":"US","area_code":"619"}'

GET /v1/voice/numbers

Lists every active or released number scoped to your tenant. Foreign-tenant numbers never leak into this list.

PATCH /v1/voice/numbers/:id

Change the assigned persona. assigned_persona_id may be null to detach. The persona must belong to the same tenant or you get 404.

DELETE /v1/voice/numbers/:id

Best-effort releases the number on Telnyx and marks the row released. Idempotent — already-released ids return 200.

Calls

POST /v1/calls

Dial outbound. The AI opens with a brief intro and pursues the supplied script as the goal of the call. Returns 201 with { id, status: 'ringing', to }.

curl -X POST https://api.senderz.com/v1/calls \
  -H "Authorization: Bearer tf_live_YOUR_KEY" \
  -d '{
    "from": "<voice_number_id>",
    "to": "+14155551234",
    "script": "Confirm Friday 2pm appointment for John Smith."
  }'

Errors:

  • 404 NUMBER_NOT_OWNED — the from voice number is not yours or is released.
  • 451 OPT_OUT — the destination has opted out of messages for this tenant.
  • 429 RATE_LIMITED — more than 10 outbound calls in the current minute.
  • 429 BLOCKED_DESTINATION — five dial failures to this destination in the last hour triggered a 1-hour block.
  • 502 TELNYX_ERROR — carrier rejected the dial.

GET /v1/calls

Filters: direction=inbound|outbound, status, from_date, to_date (YYYY-MM-DD). Pagination via limit (max 100) and offset.

GET /v1/calls/:id

Full call detail including transcript, AI summary, lead score, and has_recording. The R2 key is intentionally omitted — use /recording to play back.

GET /v1/calls/:id/events

Returns the call_events log for a single call, ordered ascending. Useful for debugging which intents fired in what order.

GET /v1/calls/:id/recording

Mints a 15-minute playback URL pointing at /v1/calls/:id/recording/stream. The stream endpoint re-checks tenant ownership and TTL before serving the MP3.

Intents

GET /v1/voice/intents

Returns built-in (talk_to_human, leave_message, end_call) plus custom intents.

POST /v1/voice/intents

Create a custom intent.

curl -X POST https://api.senderz.com/v1/voice/intents \
  -H "Authorization: Bearer tf_live_YOUR_KEY" \
  -d '{
    "name": "book_table",
    "description": "Caller wants to book a table — capture party size and time.",
    "action": "webhook",
    "action_config": { "url": "https://your-app.example.com/voice/book" },
    "language": "all"
  }'

name must be ^[a-z][a-z0-9_]{1,39}$ and unique per tenant. action is one of transfer, message, webhook, end_call. language is one of en, es-MX, tr-TR, all.

PATCH /v1/voice/intents/:id

Updates description / action / config / language / enabled. name is immutable — delete and recreate to rename.

DELETE /v1/voice/intents/:id

Removes a custom intent. Built-in names that get deleted reseed from the prompt builder’s defaults.

Billing

GET /v1/billing/voice

Current voice subscription state plus minutes used.

POST /v1/billing/voice/subscribe

Body: { "tier": "starter" | "growth" | "scale" }. Requires an active messaging subscription on the tenant.

POST /v1/billing/voice/cancel

Cancels the voice subscription item at period end. Returns 404 NOT_FOUND if voice is not active for this tenant.