Documentation
workrr Phone API
Real US phone numbers for AI agents. Calls ring your agent's endpoint first. If it doesn't answer, the caller gets voicemail and you get two webhooks: the recording right away, and the transcript when it's ready.
Building with an AI agent? Point it at /llms.txt. It has the same information in plain text, written for models.
Quickstart
Base URL: https://phone.workrr.ai. An agent can do all of these steps itself.
1. Sign up
Sign up on the website, or have your agent call the API. Either way you get an API key and a claim link.
curl -X POST https://phone.workrr.ai/v1/signup \
-H "content-type: application/json" \
-d '{"agent_name": "Support agent", "contact_email": "you@company.com"}'
A human opens the claim_url, adds a card, and accepts the terms. Until then the key returns 403 account_pending. Poll GET /v1/account until status is "active".
2. Create a line
A line is where calls go: the endpoint to ring, the webhook to notify, and the rules in between.
curl -X POST https://phone.workrr.ai/v1/lines \
-H "authorization: Bearer $WORKRR_KEY" \
-H "content-type: application/json" \
-d '{"name": "Support", "endpoint": "sip:support@agent.example",
"webhook_url": "https://agent.example/hooks/phone"}'
The response includes webhook_secret. It's shown only once, so save it.
3. Get a number
curl -X POST https://phone.workrr.ai/v1/numbers \
-H "authorization: Bearer $WORKRR_KEY" \
-H "content-type: application/json" \
-d '{"kind": "local", "area_code": "415", "line_id": "line_..."}'
This charges your card $8 now. To choose a specific number first, search GET /v1/numbers/available?area_code=415 and send its e164.
4. Call it
Call the number. If your endpoint doesn't answer, leave a voicemail and watch your webhook.
Authentication
Send your API key as a bearer token on every request except signup:
Authorization: Bearer wp_...
Keys start with wp_. Errors come back as {"error": {"code": "...", "message": "..."}}.
How calls are routed
- Extensions. If the number is shared, the caller is asked for an extension (one retry).
- Business hours. Inside hours, or if the line has none, we ring the line's
endpointforring_timeout_secs(default 20). - Forward. If nobody answers and
forward_tois set, we try it next. - Voicemail. The caller hears your greeting and a beep, then records up to 3 minutes. Recording stops after 10 seconds of silence.
- After hours. Callers go straight to voicemail, or to
forward_toifafter_hoursis"forward".
Lines
GET/v1/lines
POST/v1/lines
PATCH/v1/lines/{id}
| Field | Description |
|---|---|
name | Required. Up to 80 characters. |
endpoint | What to ring first: a SIP URI (sip:user@host) or a US number (+1XXXXXXXXXX). Leave empty to send every call to voicemail. |
forward_to | Fallback SIP URI or number, tried if the endpoint doesn't answer. |
ring_timeout_secs | 5 to 60. Default 20. |
business_hours | {"tz": "America/New_York", "days": {"mon": [["09:00", "17:00"]]}} |
after_hours | "voicemail" (default) or "forward". |
greeting_text | Voicemail greeting, up to 500 characters, read aloud. |
transcription_enabled | Default true. |
webhook_url | Where voicemail events are sent. |
Numbers
GET/v1/numbers/available?kind=local&area_code=415
GET/v1/numbers
POST/v1/numbers
PATCH/v1/numbers/{id}
DELETE/v1/numbers/{id}
Search, then order the one you want with {"e164": "+14155550142", "line_id": "..."}, or send just {"area_code": "415"} to take the first available. Toll-free needs a Business account. Without line_id the number routes to your first line.
Ordering charges the card on file $8 right away, then $8 every month. If the card is declined you get 402 payment_failed and no number is bought; if the carrier can't supply the number, the charge is refunded.
To share one number across several lines, switch it to extensions:
PATCH /v1/numbers/{id}
{"mode": "extensions", "extensions": {"101": "line_sales", "102": "line_support"}}
Deleting a number releases it and stops its billing. The current month isn't refunded.
Billing
POST/v1/billing/portal returns {"url": "..."}, a short-lived Stripe page where the account owner can update their card and download invoices. People can also sign in at phone.workrr.ai/account with their email to manage numbers, billing and API keys.
Calls and voicemail
GET/v1/calls?limit=25&before=<iso>&line_id=
GET/v1/voicemails?limit=25&before=<iso>&line_id=
GET/v1/voicemails/{id}
A single voicemail includes a signed audio_url that's good for 24 hours. Recordings and transcripts are deleted after 90 days.
Webhooks
Each voicemail sends two POSTs to the line's webhook_url, with a JSON body of {id, type, created_at, data}.
| Type | When | data |
|---|---|---|
voicemail.received | Right after the caller hangs up | voicemail_id, call_id, from, to, duration_secs, audio_url, transcript_status |
voicemail.transcribed | When the transcript is ready | voicemail_id, call_id, from, transcript |
Verifying signatures
Every webhook has a Workrr-Signature: t=<unix>,v1=<hex> header, where v1 is HMAC-SHA256 of "<t>.<raw body>" keyed with the line's webhook_secret.
import crypto from "node:crypto";
function verify(header, rawBody, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}
Failed deliveries retry with backoff. Respond with any 2xx to acknowledge.
MCP server
Connect any MCP client over Streamable HTTP at https://phone.workrr.ai/mcp with the same bearer key.
{
"mcpServers": {
"workrr-phone": {
"url": "https://phone.workrr.ai/mcp",
"headers": { "Authorization": "Bearer wp_..." }
}
}
}
Tools: get_account, search_numbers, order_number (charges $8), release_number, list_numbers, list_lines, update_routing, list_voicemails, get_voicemail, list_calls.
Limits
- Starter accounts: 1 local number. Business accounts: up to 25 numbers, including toll-free.
- Up to 3 number orders per account per 30 days (
429 order_limit). - One account per card. Prepaid cards aren't accepted.
- Signups are rate limited per network (
429 rate_limited). - Inbound only. Outbound calls and SMS aren't available yet.
- US numbers only.