BlockRun

Phone & Voice

Outbound AI voice calls and wallet-owned phone numbers — for AI agents. No telecom account, no Bland.ai signup, no Twilio dashboard. Your wallet is the phone account.

Default country: US (no regulatory friction). Other countries can be requested via the country parameter — see Country availability below.

Powered by Bland.ai (voice AI) + Twilio (carrier numbers), with x402 settlement at every step.

The Problem This Solves

An autonomous agent wants to call a restaurant to confirm a reservation, or call a vendor to verify a price. Traditionally that requires: a Twilio account, a Bland.ai account, KYC, a credit card, a long-lived API key, an outbound caller ID number you bought and manage, plus per-minute billing reconciliation.

BlockRun collapses all of it to two endpoints and one wallet:

  1. POST /v1/phone/numbers/buy — $5, get a US number for 30 days (default; other countries via country parameter).
  2. POST /v1/voice/call — $0.54, place an outbound call with an AI voice + Bland conversational task.

Wallet ownership is recorded in Firestore — only the wallet that bought a number can use it to place calls, and only that wallet can renew it.


Endpoints

EndpointMethodPriceDescription
/api/v1/phone/numbers/buyPOST$5.00Provision a new number for the calling wallet (30-day lease). US default; other countries via country parameter (may require Twilio compliance setup — see below).
/api/v1/phone/numbers/renewPOST$5.00Extend an active number's lease by 30 days
/api/v1/phone/numbers/listPOST$0.001List the calling wallet's active numbers
/api/v1/voice/callPOST$0.54Place an outbound AI call (max 30 min, default 5 min)
/api/v1/voice/call/{id}GETFreePoll call status / fetch transcript

POST /api/v1/phone/numbers/buy

Provisions a new phone number from Twilio's catalog in your chosen country, registers ownership against the calling wallet's address, and grants a 30-day lease.

Request Body

ParameterTypeRequiredDescription
countrystringNoISO 2-letter code. Default: "US". See Country availability for which other countries are pre-enabled.
area_codestringNoPreferred area code (US/CA: 3 digits). Best-effort — falls back to any in-country number if none available.

Country availability

CountryStatus
US✅ default — always works, all area codes
CA✅ usually works without friction
All others (MX, BR, AR, CL, CO, EU, AU, JP, KR, IN, etc.)⚠️ requires Twilio Regulatory Bundle approval on our account first

Twilio requires per-country KYC documentation (address proof, business identity) before allowing number purchase in most non-US/CA countries. The bundle is account-level — once it's approved by Twilio for a country, all subsequent purchases work transparently.

If you need a number outside the US: call the endpoint with your desired country code. If Twilio rejects the purchase with a regulatory error, our gateway forwards a 502 with the upstream message. Email care@blockrun.ai with the country you need — we'll provision the regulatory bundle (usually 1–5 business days for Twilio review) and let you know when the country is live.

Pricing is flat $5 / 30 days regardless of country (we absorb the Twilio cost difference). Renewal also $5.

Response

{
  "phone_number": "+14155551234",
  "country": "US",
  "owner_wallet": "0xCC8c44AD3dc2A58D841c3EB26131E49b22665EF8",
  "expires_at": "2026-06-17T12:00:00Z",
  "transaction": "0xabcd...1234"
}

The returned phone_number can be used immediately as the from field on POST /v1/voice/call. After expires_at, the number is reclaimed and re-pooled (call renew before that to keep it).


POST /api/v1/phone/numbers/renew

Extends an active number's lease by 30 days. Only the original owner wallet can renew.

Request Body

ParameterTypeRequiredDescription
phone_numberstringYesE.164 format (e.g. "+14155551234")

Response

{
  "phone_number": "+14155551234",
  "expires_at": "2026-07-17T12:00:00Z",
  "transaction": "0xabcd...1234"
}

Errors

  • 403 wrong_wallet — the calling wallet doesn't own this number. Response body includes actualOwner and your_active_numbers (the numbers your wallet does own).

POST /api/v1/phone/numbers/list

Returns the list of active phone numbers owned by the calling wallet (identity revealed via the x402 payer field).

Response

{
  "wallet": "0xCC8c44...665EF8",
  "numbers": [
    {
      "phone_number": "+14155551234",
      "country": "US",
      "expires_at": "2026-06-17T12:00:00Z",
      "days_remaining": 28
    }
  ],
  "transaction": "0xabcd...1234"
}

POST /api/v1/voice/call

Places an outbound AI conversation call. The AI voice agent dials the destination, follows the task you give it, and returns when the call ends (or hits the duration cap).

Request Body

ParameterTypeRequiredDescription
phone_numberstringYesDestination in E.164 (e.g. "+12133610872")
taskstringYesFree-text instructions for the AI — what to say, what to ask, when to hang up
fromstringNoYour wallet-owned BlockRun number (E.164). Omit if your wallet owns exactly one — it's used automatically. If you own multiple, this is required (otherwise 400 ambiguous_from). If you own none, 403 with buy hint.
voicestringNoVoice ID — see Bland voice catalog
languagestringNoISO 639-1 ("en", "es", "zh", etc.)
max_durationintegerNoMax call duration in minutes. Default: 5. Cap: 30.
first_sentencestringNoExact opener — overrides AI's default greeting
wait_for_greetingbooleanNoWait for callee to speak first before AI starts (useful for IVRs). Default: false
interruption_thresholdintegerNoMilliseconds of silence before AI can interrupt. Default: 100

Response

{
  "call_id": "01HXY9...",
  "from": "+14155551234",
  "to": "+12133610872",
  "status": "started",
  "expected_duration_minutes": 5,
  "transaction": "0xabcd...1234"
}

The call is asynchronous — status: "started" means dialing is in progress. Poll GET /v1/voice/call/{call_id} to track progress.

Errors

  • 403 no_owned_numbers — your wallet hasn't bought a number yet. Buy one at /v1/phone/numbers/buy.
  • 403 wrong_wallet — you passed a from you don't own. Response includes your_active_numbers.
  • 400 ambiguous_from — wallet owns multiple numbers; specify from explicitly.

GET /api/v1/voice/call/{call_id}

Poll for call status, transcript, and final outcome. Free — no payment required.

Response

{
  "call_id": "01HXY9...",
  "from": "+14155551234",
  "to": "+12133610872",
  "status": "completed",
  "answered_by": "human",
  "call_length": 1.42,
  "call_ended_by": "USER",
  "ended_by": "USER",
  "ended_by_reason": "The callee (the person you called) hung up. This is normal — humans typically end calls once they've answered your question. Not a system error.",
  "summary": "Called Andy to ask him to take a break. Andy agreed and said he'd log off at 6pm.",
  "transcript": [
    { "role": "assistant", "text": "Hi Andy, this is Vicky's assistant calling..." },
    { "role": "user", "text": "Oh hey, what's up?" }
  ],
  "recording_url": "https://..."
}

status values: started, ringing, in-progress, completed, failed, no_answer, busy.

ended_by — why the call ended

BlockRun synthesizes an ended_by field on the GET response so you can distinguish "the callee hung up" (normal) from "the system errored" (bug). Always check this field before assuming a failure.

ended_byMeaningCharge?
USERThe callee hung up. Most common outcome. After they answer your AI's question, humans typically end the call. The transcript will look "cut off" because the AI was mid-response — that's expected. Not a system error.Yes (already settled)
ASSISTANTThe AI ended the call gracefully — task complete, exit-instruction triggered, or max_duration approached.Yes
TIMEOUTHit the max_duration cap. Pass a larger value next time if you expect long conversations.Yes
NO_ANSWERLine rang out, no human picked up.Yes
BUSYDestination was busy.Yes
VOICEMAILVoicemail picked up — the AI hung up.Yes
ERRORBland/Twilio surfaced an explicit error (carrier, routing, etc.).No — failed-call legs are not charged.
IN_PROGRESSCall hasn't ended yet — keep polling.Pending

If you keep seeing ended_by: USER and want longer conversations, update your task to give the AI a clearer exit instruction:

Call my friend Andy at +12133610872. Ask him whether he wants Italian or Sushi for dinner tonight. After he answers, confirm the restaurant choice back to him, then thank him politely and end the call. Do not keep talking past the answer.

Without an explicit "end the call after X" instruction, the AI tends to keep volunteering follow-up questions, and the callee — having already answered what they thought you wanted — hangs up first.


SDK Usage

TypeScript

import { LLMClient } from '@blockrun/llm';

const client = new LLMClient({ privateKey: process.env.BASE_CHAIN_WALLET_KEY });

// 1) One-time setup: buy a number
const number = await client.phoneBuy({ country: 'US', area_code: '415' });
console.log('Got number:', number.phone_number);

// 2) Place a call (uses the bought number automatically)
const call = await client.voiceCall({
  phone_number: '+12133610872',
  task: 'Call Andy and ask him politely to take a break. Be friendly. Hang up after he confirms.',
  max_duration: 3,
});

// 3) Poll until done (free)
let status;
do {
  await new Promise(r => setTimeout(r, 5000));
  status = await client.voiceCallStatus(call.call_id);
} while (status.status === 'started' || status.status === 'in-progress');

console.log('Call ended:', status.summary);

Python

from blockrun_llm import LLMClient

client = LLMClient()

# One-time: buy a number
number = client.phone_buy(country='US', area_code='415')

# Place a call
call = client.voice_call(
    phone_number='+12133610872',
    task='Confirm the 7pm reservation for two under Vicky.',
    max_duration=2,
)

# Poll
import time
while True:
    status = client.voice_call_status(call['call_id'])
    if status['status'] in ('completed', 'failed', 'no_answer', 'busy'):
        break
    time.sleep(5)

print(status['summary'])

MCP (Claude Code / OpenClaw)

Use blockrun_wallet to confirm I own a phone number, then use the BlockRun voice tool to call +14155551234 and tell them I'll be 10 minutes late.

(Voice MCP tools may ship under different names — check the BlockRun MCP tool list.)


Use Cases

1. Reservation confirmer ($0.54 per restaurant)

Agent calls a restaurant to confirm a booking. Hangs up after confirmation.

2. Vendor price-check bot ($0.54 per vendor)

Procurement agent calls suppliers, reads off a SKU, asks for current price + lead time, logs the answer.

3. AI receptionist for your wallet's inbound (coming soon)

The wallet-owned numbers can also receive inbound calls — feature is in development. Track awesome-blockrun/ROADMAP.md.

4. Wellness check bot ($0.54)

Agent calls a family member on a schedule, has a short conversation, summarizes the call into a Slack message.


Pricing

ActionPriceNotes
Buy a number$5.0030-day lease, US or CA
Renew$5.00+30 days
Place a call$0.54Up to 30 min, default 5 min
Poll status / fetch transcriptFreeGET /v1/voice/call/{id}

All settled in USDC on Base (or Solana) via x402.

Why flat-rate for calls?

Competitors (StablePhone et al) charge $0.54 per minute — a 30-min call becomes $16+. BlockRun charges $0.54 per call regardless of length (up to the 30-min cap). The math:

  • Short calls (<1 min, e.g. "is it open?"): we lose pennies, you win big.
  • Long calls (15+ min): we eat the upstream Bland cost, but the call cost is bounded for you.

This is intentional — autonomous agents need predictable per-action costs, not per-minute billing they have to model.


Required Setup

  1. Fund your wallet with USDC on Base — at least $5.55 covers one number + one call.
  2. Buy a number — first call creates it under your wallet's ownership.
  3. Place a call — uses your owned number as caller ID automatically.

The number is owned by the wallet address that paid the buy x402. Lose your wallet's private key, you lose the number after expiry.


Limitations

  • Number provisioning: US is the default and always works. Other countries are supported via the country parameter but typically require Twilio Regulatory Bundle (KYC) approval first — see Country availability. Request a country via care@blockrun.ai.
  • Call destinations: any international destination Bland.ai supports — no geo restriction on the phone_number (callee) field, only on the from (caller-ID) number.
  • Outbound only. Inbound receive is on the roadmap.
  • 30-min hard cap per call. Longer dial-and-stay use cases require a custom integration.
  • One Bland voice model per call. Custom voice cloning requires upstream Bland account (we can't pass that through anonymously yet).

Error Handling

CodeDescription
200Success
400Bad request — missing E.164, invalid from, task too short, ambiguous_from
402Payment required — sign and retry
403no_owned_numbers / wrong_wallet — buy or use a different from
404Unknown call_id on poll
429Upstream rate limit (Bland or Twilio) — Retry-After header set, not charged
502Upstream Bland/Twilio error — not charged

Links