> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lupin.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Phone, SMS and calls

> Find a number, send your first SMS, and control a voice call.

# Phone

Phone requires an operator-configured Telnyx account, `TELNYX_API_KEY`,
`TELNYX_MESSAGING_PROFILE_ID` for SMS, and `TELNYX_CONNECTION_ID` for voice.
The operator must configure Telnyx callbacks to the API's `/v0/telnyx/webhooks`.
Missing provider configuration returns an explicit unavailable response.

## Choose a number before purchasing

```bash theme={null}
lupin numbers search --country US --area-code 415
# Review the returned number, capabilities and recurring price.
lupin numbers buy --phone-number +14155550123 --label Support
lupin sms send --from NUMBER_ID --to +14155550124 --text 'Hello'
lupin numbers messages --number-id NUMBER_ID
```

Purchasing and releasing numbers affect the provider account. Review the current
price and billing terms before purchase. Inventory availability and SMS/call
charges depend on the country and provider; no fixed price is guaranteed here.

## Voice

Live voice conversations use the workspace's existing Telnyx number and an explicitly configured voice profile. The current media gateway uses Scribe v2 Realtime transcription, Eleven Realtime v1 Max for conversational responses and V4 Turbo synthesis. Workspace operations are delegated to Lupin's configured agent backend, with its existing model, tool discovery and permissions. Consult a call's captured stack for the actual configuration; provider settings can evolve.
Sensitive actions create exact pending approvals in the authenticated workspace
console; saying an approval phrase during a call does not execute an action.
Use `GET /v0/phone/status` to see enabled numbers, allowed destinations, the
remaining monthly voice seconds and the maximum call duration. The iMessage
agent exposes this as `phone-status` and requests human approval before dialing.

```ts theme={null}
const call = await lupin.calls.create({
  from: numberId,
  to: "+14155550124",
  voice: true,
  purpose: "Have a short conversation with me in French.",
  max_duration_seconds: 120,
}, crypto.randomUUID());
```

`purpose` describes the conversation goal. Use `say` only for exact words the human requested the agent to deliver. Do not supply both. Voice calls have a maximum duration of two minutes, one
active call per workspace, and an explicit monthly seconds allowance. Telnyx
and ElevenLabs apply their own provider charges. Retail phone overages are
not enabled by this integration, and no Stripe subscription is created.
If a workspace spending budget has no verified all-in voice cost ceiling,
new voice calls are blocked rather than bypassing the budget.

Incoming calls from the profile's allowed peers can be answered automatically.
Caller ID alone does not grant access to private tools or memory: incoming
callers are asked to request a callback through authenticated iMessage or the
dashboard. Private agent access is available on those outgoing callbacks.
Other callers do not activate the AI. Audio recording is off by default.
Signed ElevenLabs post-call transcripts appear in call details and the
dashboard's number activity, subject to the workspace content retention policy.
Expand **Stack & timings** on a voice call to inspect its captured model
configuration, call setup timings, and the response metrics supplied by
ElevenLabs, plus the actual Lupin model and backend/tool timings. Workspace-tool backend completion and model first-token timings are labelled separately. Missing measurements are labelled explicitly. The detail loads on
demand; refreshing it never places another call. Setup timings and conversation
metrics can overlap and do not add up to the delay heard on the telephone.
Live voice calls support the `hangup` control action; speaking and turn-taking
are handled by ElevenLabs. Ending a call is reported as requested until a
provider callback or status read confirms closure.

Operator setup uses the `voice-setup` deployment workflow: inspect first, then
configure with `ELEVENLABS_API_KEY` and an exact allowed peer supplied through
private setup metadata. Configuration preserves the owned Telnyx number and Call Control routing and pins Telnyx's signature-verification key. SIP and media-gateway transports have separate operator validation paths; availability of one does not imply that another is active. API keys and SIP
passwords are never included in call responses. Agent callers receive no
privileged workspace tools through caller ID alone.

```bash theme={null}
lupin calls create --from NUMBER_ID --to +14155550124
lupin calls get --call-id CALL_ID
lupin calls action --call-id CALL_ID --action speak --text 'Hello'
lupin calls action --call-id CALL_ID --action start_recording
lupin calls action --call-id CALL_ID --action hangup
```

Other actions: `play --audio-url URL`, `stop_recording`, `transfer --to E164`.
`calls.get` exposes `recording_url`, `transcript`, and `cost_cents` when the
provider supplies them. Null means unavailable. Recordings/transcripts require
provider configuration and callbacks; a successful dial does not imply an
answered call. Apply the consent requirements of your operating region.

```ts theme={null}
const call = await lupin.calls.create({ from: numberId, to: "+14155550124" });
await lupin.calls.action(call.call_id, { action: "hangup" });
await lupin.numbers.release(numberId);
```

For `unknown_sender`, use an active owned number ID or its E.164 value.
For `telnyx_not_configured`, ask the operator to configure the provider.
For `call_not_active`, refresh the call before attempting another action.
Release a number only after confirming it is no longer needed; provisioning a
replacement does not guarantee the same phone number.

## Durable actions and retry safety

With durable storage enabled, provider-backed phone mutations require
`Idempotency-Key` (1–200 characters). SDK methods generate a key for each call;
pass an explicit stable key as the final argument before implementing retries.
CLI phone commands accept `--idempotency-key`. Reuse the same key and payload
when retrying the same action. A new method invocation with a fresh auto-generated
key represents a new action and may incur a new charge.

```ts theme={null}
const operationKey = crypto.randomUUID(); // retain this key before submitting
await lupin.sms.send({ from: numberId, to: "+14155550124", text: "Hello" }, operationKey);
```

The API durably journals intent before contacting Telnyx, records provider
references, then persists resource state before returning success. If the provider outcome itself cannot be durably recorded, number
purchase and dial failures attempt number release or hangup.
SMS sends cannot be undone. Known provider outcomes include a private local
projection: same-key retries and the scheduled reconciler restore local state
and complete the journal atomically without issuing the provider action again. A response with `reconciliation_required` (503, or
409 when replaying unfinished work) includes `operation_id` and any known
`provider_reference`. Stop submitting paid actions and give this operation ID to
support. Do not change the idempotency key to bypass this guard. When the provider outcome was not recorded, operators
reconcile `product_records` rows with kind `phone_operation` against Telnyx;
a provider-accepted request can have an uncertain local outcome after an outage.
Definitive provider validation rejection is saved and replayed with its original
4xx response. A timeout or server error remains uncertain and is not reissued.
Project keys can access only numbers/calls/SMS in their project; inbox-scoped
keys cannot access phone resources. SMS threads belong to the sending number's
project, including when an organization key sends the message.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.