For the complete documentation index, see llms.txt. This page is also available as Markdown.

Floe Phone — numbers & telephony

Give an agent a real US phone number in one API call. The number binds 1:1 to the agent, the monthly rental debits the agent's Floe balance, and every leg of a live voice call — carrier minutes, transcription, model tokens (hosted mode), speech synthesis — meters as separate line items on the same ledger, governed by the same spend controls as every other vendor.

No carrier account, no telephony credentials, no per-vendor billing. One Floe key, one balance, one set of caps.

When someone dials the agent's number, Floe answers into its media pipeline: caller audio is transcribed (Deepgram), the transcript runs through the agent's model via keyless inference (hosted mode — in webhook mode your own backend supplies the reply instead), and the reply is spoken back (ElevenLabs) — all in real time, with barge-in support. Outbound works the same way via POST /v1/calls.

Pricing

Item
Price

US local number

$2.00 / month flat (charged at purchase, then monthly)

Inbound call transport

$0.0089 / minute

Outbound call transport

$0.0147 / minute

Live-call transcription (STT)

~$0.0045 / minute

Live-call speech (TTS)

$0.0000525 / character

Live-call model (LLM)

the model's own keyless-inference rate — hosted mode only

Usage prices are upstream cost plus Floe's standard 5% margin; the number rental is a flat monthly price. The rental price is locked in when you buy the number; catalog price changes only affect future purchases. Each call produces itemized ledger rows (phone://{number}/call/{callId} for transport, …/stt and …/tts for the voice legs, plus — on hosted-mode calls — the model's own gateway rows; webhook mode uses your model, so no LLM leg is billed) — you can see exactly where a cent went.

Runaway calls are cut off mid-flight. An upper bound (default $2.00, or the call's own maxSpendRaw if you set one — see Per-call budgets) is reserved when a call starts; spend is metered live during the call, and if the next turn wouldn't fit — or a spend policy or session cap would breach — Floe hangs up the call. A carrier-side usage trigger acts as an async backstop and suspends the account's phone service if carrier spend crosses its monthly threshold.

No auto-recharge, by design. If the agent balance can't cover a monthly renewal, the number enters a grace period (7 days) and keeps working; if the balance isn't funded by the end of it, the number is released.

Authentication

All endpoints below use your developer key (floe_live_…) as a Bearer token — the same auth as the rest of the /v1/developer surface. Numbers are reached through the agent that owns them; a number under another developer's agent returns 404.

Authorization: Bearer floe_live_...

Buy a number

One US local voice number per agent, bound at purchase, provisioned instantly. areaCode is optional — omit it for any available US number.

POST /v1/developer/agents/{agentId}/numbers

Response 201:

The first month's rental (monthlyRentalRaw, raw 6-decimal USDC — 2000000 = $2.00) is debited from the agent balance at purchase and returned in the X-Floe-Cost-USDC response header. The debit appears on the ledger as phone://{number}/rental.

Errors: 402 insufficient_balance (fund the agent first), 402 policy_exceeded / spend_limit_exceeded (a spend policy blocked the debit), 409 number_exists (the agent already has a number), 409 no_numbers_available (try another area code), 409 agent_unavailable (agent suspended or closed).

List numbers

GET /v1/developer/agents/{agentId}/numbers

Returns all of the agent's numbers, newest first, including released history. status is one of:

  • active — provisioned, rental current.

  • grace — the last renewal debit failed; the number works until graceUntil, then it is released. Fund the agent balance to keep it.

  • released — gone (developer release, non-payment, or agent winddown — see releaseReason). History rows are kept forever.

Release a number

DELETE /v1/developer/agents/{agentId}/numbers/{numberId}

Irreversible — the number returns to the carrier pool and may be claimed by someone else. Call history and spend attribution are preserved. No refund for the current rental period. Releasing an already-released number is a no-op success.

Call history

GET /v1/developer/agents/{agentId}/numbers/{numberId}/calls

Recent calls touching the number, most recent first:

Usage & spend

GET /v1/developer/agents/{agentId}/numbers/{numberId}/usage?days=30

Per-number spend time-series straight from the Floe ledger — rental debits plus every call's transport, STT, and TTS legs. days is 1–365, default 30. Amounts are raw 6-decimal USDC strings.

Outbound calls

POST /v1/calls — authenticated with the agent's key (floe_…), so agents can place calls autonomously under their own spend controls. The agent's number is the caller ID; when the callee answers, the same voice pipeline runs.

Response 201: { "callId": "CA…", "from": "+14155550123", "to": "+14155559876", "status": "queued", "taskId": "ca…" }. A call that's never answered costs nothing — billing starts when the media stream opens. There is also a dashboard-session variant, POST /v1/developer/agents/{agentId}/numbers/{numberId}/test-call, which powers the one-click "the agent calls you" test.

Per-call budgets & attribution

Every call is a task. Each ledger row a call produces — transport, STT, TTS, and (hosted mode) the LLM leg — is stamped with one shared task id: the value of the X-Floe-Task-Id header if you sent one on POST /v1/calls, otherwise the lowercased callId. The id is echoed back as taskId in the 201 response. That means one query over the ledger by task id returns the complete itemized cost of one call — and if your webhook-mode backend tags its own LLM and tool calls with the same id (pass X-Floe-Task-Id: <callId lowercased> on its keyless-inference and /v1/proxy/fetch calls), the brain's spend lands in the same rollup. One call, one task id, every leg.

Optional request extras on POST /v1/calls:

Field
Where
What it does

X-Floe-Task-Id

header

Task id stamped on every ledger leg of this call (≤128 chars, lowercased). Defaults to the lowercased call id.

X-Floe-Customer-Id

header

Opaque end-customer attribution (≤128 chars, lowercased) — lands on the same rows for the cross-source ledger.

maxSpendRaw

body

Per-call reserve cap, raw 6-decimal USDC (e.g. "500000" = $0.50). Clamped to the platform per-call ceiling ($2.00 default), rejected with 400 max_spend_too_low if it can't cover one minute of calling.

To make a per-call budget enforced rather than just attributed, create a task spend policy whose matchKey is the task id — the reserve at answer, every hosted LLM turn, and any tool call tagged with that task id are then gated by it. The pre-dial check evaluates the task policy too, so an exhausted task budget denies with 403 before the callee's phone rings.

Call status & hangup

GET /v1/calls/{callId} — agent key. Poll a placed call's progress (dialers use this to detect call end):

status is pending (queued / ringing / never answered), in_progress (live), ending (a hangup was issued and is being processed — transitions to a terminal status shortly), completed, or failed; terminal is true once the call can no longer change. Only the owning agent's calls are visible — anything else reads as pending.

POST /v1/calls/{callId}/hangup — agent key. End a live call (or cancel one that is still ringing) from outside the conversation:

Returns 202 when the hangup was issued, or 200 with "terminal": true when the call had already ended (idempotent). This is the out-of-band control lever — a compliance system killing a call after an opt-out, or a dialer cancelling a runaway campaign. For ending a call from within the conversation, use the webhook "end" directive below.

Pre-dial budget check

Before the call is placed, POST /v1/calls runs an admission check so an over-budget or suspended agent can't ring a callee's phone only to be cut off the moment the call connects. If the agent is suspended, or its balance can't cover roughly one minute of calling, or a spend policy blocks the call, the request is denied with 403 before the call is placed — you get no callId and the callee's phone never rings.

Deny reasons include insufficient_balance (top up the agent first), policy_exceeded (a spend policy blocks the call), and the suspended states credit_frozen / credit_line_expired (resolve billing or renew the credit line).

This is admission control at the start of the call, and it's deliberately conservative — the authoritative money gate is the reserve taken when the call is answered, so the pre-dial check rejects only clearly-unaffordable or blocked dials. It's distinct from live metering: once a call is answered, Floe meters it per second and ends it if continuing would breach the cap (see above). The pre-dial check just avoids ringing a callee for a call that can't run.

Voice settings — hosted vs webhook

GET | PATCH /v1/developer/agents/{agentId}/voice

Two ways to run the conversation, switchable any time — the setting is read at call setup, so a PATCH applies to the next call with zero downtime:

  • hosted (default) — Floe runs the model through keyless inference using your systemPrompt. No server needed. The LLM leg bills per-token like any gateway call.

  • webhook — Floe streams each finished caller utterance to your webhookUrl and your backend replies with NDJSON text chunks. You bring your own model — no LLM leg is billed.

The webhook contract

Each finished caller utterance arrives as a POST to your webhookUrl:

from/to are the real call parties — on an outbound call to is the callee, on inbound from is the caller — and direction tells you which kind of call this is without keeping your own call-id map. recentHistory is the last 10 turns (your backend keeps its own state if it wants more). Floe sends no auth header on this request, so put a secret in the URL path (https://your-server/voice/<random-token>) and require it.

Reply with NDJSON, one JSON object per line:

  • {"text":"…","interim":true} — progress lines; used only as a fallback if no final line arrives.

  • {"text":"…"} — final lines; concatenated in order and spoken (capped at 1500 characters per turn).

  • {"text":"Goodbye!","end":true} — speak the final text, then hang up gracefully. A text-less {"end":true} hangs up silently. This is the in-band call-end directive — the natural way to finish a conversation, honor an opt-out ("take me off your list" → confirm → end), or wrap up when a budget is nearly exhausted. end is honored on final lines only.

Your backend has 30 seconds to respond; a non-2xx, timeout, or empty reply ends the call (llm_refused). Keep replies fast — the caller is waiting on the line.

PATCH fields (all optional; empty string clears): voiceMode, systemPrompt (hosted), beginMessage (spoken on connect), voice (TTS voice id), model (gateway model slug, default openai/gpt-5.4-mini), webhookUrl (required for webhook mode, https only).

Call lifecycle webhooks

Subscribe a developer webhook to call.* to close the loop without polling. Floe Phone emits:

  • call.started — the call was answered. Payload: { agentWalletAddress, callId, phoneNumber, direction, from, to }.

  • call.ended — the call finished and settled. Payload: { agentWalletAddress, callId, phoneNumber, direction, reason, durationSeconds, transportRaw, sttRaw, ttsRaw, taskId, customerId } — the per-leg costs are the settled ledger amounts, so this one event is a complete per-call cost receipt. reason is the termination cause (call_ended, agent_ended, budget_exhausted, reserve_exhausted, max_duration, llm_refused, …). If the media session crashed and the carrier's status callback settled the call instead, the same event arrives with reason: "backstop_settled", backstop: true, and sttRaw/ttsRaw of "0" (unknown legs settle toward you, not against you).

Both events correlate on callId. Delivery is at-least-once — retries of the same delivery share a stable X-Floe-Delivery-Id, so deduplicate on that header and route your handler on the event field; use callId only to correlate the lifecycle events with each other.

Dashboard

Everything above is also in the developer dashboard: open an agent and use the Floe Phone panel to buy a number by area code, switch voice modes, set the prompt and greeting, run a one-click test call, watch renewal state, and see per-call spend. The vendor card lives in the marketplace under Telephony → Floe Phone.

Last updated