REST API
Short answer: The REST API at https://callforme.tel/v1 has the same semantics as the MCP server. POST /calls places a call, GET /calls/:id?wait=45 polls it, POST /calls/:id/answer replies to a mid-call question. Authenticate with Authorization: Bearer cfm_.... The OpenAPI spec is at https://callforme.tel/openapi.json.
Base URL and auth
https://callforme.tel/v1
Send your key as a header:
Authorization: Bearer cfm_your_key
Two ways to get a key:
- From the first call. An agent with no key calls
POST /calls, gets a one-time setup link (below), and the retry returnsaccount_keyonce. Keep it in theCALLFORME_KEYenv var or a secret store, never in your agent's memory. - From the account page. Create one at callforme.tel/account and give it to your agent.
Agents with a shell can read https://callforme.tel/skill.md, which walks through this flow with curl. Building your own agent? How to add phone calls to your own AI agent has a TypeScript version you can run on a demo line.
OpenAPI: https://callforme.tel/openapi.json
Endpoints
| Method | Path | Same as MCP tool | Key needed |
|---|---|---|---|
| POST | /calls |
place_call |
To dial |
| GET | /calls/:id?wait=45 |
get_call |
Yes |
| POST | /calls/:id/answer |
answer_question |
Yes |
| POST | /calls/:id/hangup |
hang_up |
Yes |
| POST | /calls/:id/redial |
redial |
Yes |
| POST | /setups/:code/wait?wait=45 |
wait_for_setup |
No |
| POST | /calls/around |
call_around |
To dial |
| GET | /batches/:id?wait=45 |
get_batch |
Yes |
| POST | /batches/:id/stop |
stop_batch |
Yes |
| POST | /calls/:id/share |
share_call |
Yes |
| POST | /calls/plan |
plan_call |
No |
| GET | /businesses?query=&near= |
find_business |
No |
| GET | /account |
account |
Yes |
| GET | /calls |
list_calls |
Yes |
| GET | /pricing |
pricing |
No |
Place a call
curl -X POST https://callforme.tel/v1/calls \
-H "Authorization: Bearer $CALLFORME_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+14697707412",
"business_name": "Maple Street Pizza",
"goal": "Book a table for 4 this Friday between 7 and 8:30pm.",
"questions": ["Is a booth available?", "Confirmation number"],
"if_asked": "Any time from 7 to 8:30 is fine. A window table is fine if there are no booths.",
"on_behalf_of": "Sam"
}'
That number is Maple Street Pizza, a free demo line: you can run this exact request as is, even before you have a key (leave $CALLFORME_KEY unset).
Response:
{ "status": "calling", "call_id": "call_8f2k1", "answer_window_seconds": 45, "poll_after_seconds": 0 }
All place_call fields are accepted: phone, goal, business_name, details, questions, if_asked, on_behalf_of, callback_number, constraints, voicemail_message, transfer_to, language, setup_code, account_key. See the MCP reference for what each does. REST also takes webhook_url (webhooks).
Retries without double calls
Send an Idempotency-Key header (a UUID works) to make a retry safe. If your request times out and you send it again with the same key and the same body within 24 hours, you get the first response back, with Idempotent-Replayed: true, and nobody is dialed twice. The key is spent only once a call starts: if the first try returned a 402 setup link or a 400, the same key works again after you fix it. Reusing a key with a different body returns 409 IDEMPOTENCY_MISMATCH. POST /v1/calls/around takes the header too.
curl -X POST https://callforme.tel/v1/calls \
-H "Authorization: Bearer $CALLFORME_KEY" \
-H "Idempotency-Key: 3f6c1b1e-7d2a-4c1e-9b8a-5e2f0c9d4a11" \
-H "Content-Type: application/json" \
-d '{"phone": "+14697707412", "goal": "Ask what time they close tonight."}'
Without the header, an identical request from the same account within 2 minutes still returns the call already in progress.
Without a key (first call)
curl -X POST https://callforme.tel/v1/calls \
-H "Content-Type: application/json" \
-d '{"phone": "+12145552961", "goal": "Ask if they are open on Thanksgiving and until what time."}'
HTTP 402:
{
"status": "needs_setup",
"setup_url": "https://callforme.tel/go/QF6S-2U4Z",
"setup_code": "QF6S-2U4Z",
"message_for_user": "To place this call, open https://callforme.tel/go/QF6S-2U4Z and add $5 of call credit (about 5 calls)."
}
Show message_for_user to the person. The page shows the exact call; they add $5 (card saved, auto-reload on). Setup codes last 24 hours. After they finish, send the same request with "setup_code": "QF6S-2U4Z". The call dials and the response includes account_key once. Store it in CALLFORME_KEY (or a secret store) and send it as the Bearer header from then on. See setup links.
Try the demo line (free)
curl -X POST https://callforme.tel/v1/calls \
-H "Content-Type: application/json" \
-d '{"phone": "+14697707412", "business_name": "Maple Street Pizza", "goal": "Ask how long a large pepperoni takes for pickup."}'
Maple Street Pizza is a fictional restaurant. Calls to it need no key and no payment, up to 3 a day; accounts that have added credit get 10 a day for testing. There are also three fictional auto shops for quote calls: Oak Street Auto (+14694022653), Cedar Lane Tire & Brake (+14694379838) and Elm Avenue Garage (+14693006608), plus a gym that handles cancellations, Ironwood Fitness (+14692538241). See the demo lines page.
Poll a call
curl "https://callforme.tel/v1/calls/call_8f2k1?wait=45" \
-H "Authorization: Bearer $CALLFORME_KEY"
{
"call_id": "call_8f2k1",
"status": "ended",
"pending_question": null,
"answers": [
{ "question": "Is a booth available?", "answer": "No booths Friday. Window table instead." },
{ "question": "Confirmation number", "answer": "MS4412" }
],
"unanswered_questions": [],
"outcome": {
"success": true,
"type": "booked",
"summary": "Booked 4 at 7:45 PM Friday under Sam, window table, confirmation MS4412.",
"confirmation_number": "MS4412",
"booked_time": "Friday 7:45 PM",
"deviations": ["Window table instead of a booth"]
},
"duration_seconds": 58,
"cost": "$1.00"
}
status is one of queued, ringing, in_progress, ended. wait can be 0 to 50. answers keeps the order of your questions, with null for anything the call didn't get. Add transcript=new to get only the lines since your last poll, full for all of it, or none. The full field list, including every outcome.type, is in the MCP reference.
For the account that placed the call, an ended call also includes recording_url, an audio file of the whole call, and listen_url, a private page that plays it next to the summary and transcript (handy to show a person; neither is returned to anonymous connections). The transcript and recording are also on your account page.
If you didn't pass callback_number, a real call also includes callback_line: CallForMe's number and the 4-digit extension for that call, which the assistant gives a business that asks how to reach the customer. If they call back and leave a message, it appears in callbacks on this call. See callbacks.
While a call is live you can steer it by text with POST /calls/{id}/answer, and you can join it yourself: pass transfer_to with your phone and say in the goal to connect you, and the assistant gets through the menus and hold, gives the opener, then hands the call to you and drops off (a cold transfer).
Webhooks
Instead of polling, add webhook_url to POST /v1/calls (or /v1/calls/around, where every call in the batch gets its own delivery). Once the call's outcome is final, CallForMe POSTs it there:
{ "type": "call.ended", "created": 1794560000, "data": { "call_id": "call_8f2k1", "status": "ended", "answers": [...], "outcome": { "success": true, "summary": "..." }, "cost": "$1.00" } }
data is the same object GET /v1/calls/{id} returns, with the full transcript. Calls nobody answered are delivered too, with their outcome.
- https only, to a public hostname. Requests to private or local addresses are refused.
- Any 2xx counts as received. Anything else, or no answer within 10 seconds, is retried after 30 seconds, 5 minutes and 30 minutes.
- Status:
GET /v1/calls/{id}showswebhookwithstatus(pending,delivered,failed),attemptsandlast_error.
For the whole pattern (webhooks, idempotency keys and which failed calls to retry), see webhooks, retries and no double calls.
Verify the signature
Each delivery has a CallForMe-Signature: t=<unix time>,v1=<hex> header. v1 is an HMAC-SHA256 of <t>.<raw body> keyed with your webhook_signing_secret from GET /v1/account. Compare in constant time and reject old timestamps:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const want = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return want.length === v1.length && timingSafeEqual(Buffer.from(want), Buffer.from(v1));
}
Answer a mid-call question
When pending_question is set:
curl -X POST https://callforme.tel/v1/calls/call_8f2k1/answer \
-H "Authorization: Bearer $CALLFORME_KEY" \
-H "Content-Type: application/json" \
-d '{"answer": "The date of birth on the prescription is March 3, 1988."}'
The business is waiting on the line. You have about 45 seconds; after that the assistant tells them the customer will follow up on that point and carries on. The same endpoint also steers a live call at any time, with no pending question: {"answer": "Make it 6 people instead of 4."}. If the call has transfer_to, {"answer": "Connect me now"} brings the user in right away (or once a person answers, if it is still in a menu or on hold); without transfer_to it returns NO_TRANSFER_TO with the redial to use.
When the business sends a verification code
Banks, airlines and carriers often text a one-time code to the account holder mid-call. POST /calls/{id}/answer refuses codes (SENSITIVE_ANSWER), and the assistant never reads one. Pass transfer_to with the account holder's phone on these calls: when the code comes up, the assistant connects them to read it. Without it, the assistant gets a reference number and the result tells you to redial with transfer_to. See verification codes.
Call around (several places)
curl -X POST https://callforme.tel/v1/calls/around \
-H "Authorization: Bearer $CALLFORME_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "pharmacy",
"near": "75024",
"count": 8,
"goal": "Find out what time the pharmacy counter closes tonight.",
"questions": ["What time does the pharmacy counter close tonight?"]
}'
Returns 201 with a batch_id. The server finds the places, dials up to concurrency at a time (default 5, max 10), waits for 7am at places where it's 10pm to 7am, and stops early with stop_after or max_spend_usd. More than 10 places returns a preview first; send the same body with "confirm": true after the user has seen it. "dry_run": true previews without dialing.
Follow it:
curl "https://callforme.tel/v1/batches/BATCH_ID?wait=45" \
-H "Authorization: Bearer $CALLFORME_KEY"
The response has totals, a results table, spent, pending_questions (answer each with POST /calls/:id/answer) and next_step. GET /batches without an id returns your latest batch. Stop dialing new places with POST /batches/BATCH_ID/stop; add {"hang_up_active": true} to end calls in progress too. Details in the call around guide.
Hang up
curl -X POST https://callforme.tel/v1/calls/call_8f2k1/hangup \
-H "Authorization: Bearer $CALLFORME_KEY"
Share a call
curl -X POST https://callforme.tel/v1/calls/call_8f2k1/share \
-H "Authorization: Bearer $CALLFORME_KEY" \
-H "Content-Type: application/json" \
-d '{"public": true}'
{
"ok": true,
"url": "https://callforme.tel/c/r8x2kq",
"message": "Anyone with this link can read the transcript and result. Phone numbers and emails are hidden."
}
Works once the call has ended. The page shows the transcript and result with phone numbers and email addresses hidden. Send {"public": false} to take it down.
Plan a call (no key)
curl -X POST https://callforme.tel/v1/calls/plan \
-H "Content-Type: application/json" \
-d '{
"phone": "+19725550100",
"goal": "Book a full groom for a 60 lb goldendoodle next Saturday.",
"details": "Owner name Sam."
}'
Returns the opening line, a checklist, likely questions the business will ask that you haven't covered, whether the business is open now, and a cost estimate.
Find a business (no key)
curl "https://callforme.tel/v1/businesses?query=dog%20groomer&near=Plano%2C%20TX"
Returns name, phone, address, open now, hours today and rating for each match.
Account
curl https://callforme.tel/v1/account \
-H "Authorization: Bearer $CALLFORME_KEY"
Returns your balance, about how many calls it covers, whether auto-reload is on, recent spend and the day it runs out at that pace, and a top-up link. When the balance is low with auto-reload off, or calls are paused under your floor, warnings says so: check it before a batch of calls.
List calls
curl https://callforme.tel/v1/calls \
-H "Authorization: Bearer $CALLFORME_KEY"
Pricing (no key)
curl https://callforme.tel/v1/pricing
A full script
#!/usr/bin/env bash
set -euo pipefail
KEY="$CALLFORME_KEY"
ID=$(curl -s -X POST https://callforme.tel/v1/calls \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"phone":"+14697707412","goal":"Ask how long a large pepperoni takes for pickup and the price.","on_behalf_of":"Sam"}' \
| jq -r .call_id)
while true; do
R=$(curl -s "https://callforme.tel/v1/calls/$ID?wait=45&transcript=none" -H "Authorization: Bearer $KEY")
S=$(echo "$R" | jq -r .status)
[ "$S" = "ended" ] && [ "$(echo "$R" | jq -r '.outcome != null')" = "true" ] && { echo "$R" | jq '{outcome, answers}'; break; }
done
The script calls the demo line, so it runs free. Swap in a real number when you're ready.
Limits
| Limit | Value |
|---|---|
| Numbers | US and Canada only; emergency, N11 and premium numbers are blocked |
| Same number | 3 calls per account per day, 10 across all users |
| Call length | 30 minutes, including hold |
| Call around | 1 to 100 places per request, up to 10 at a time |
| Late calls | Single calls dial any time (with a warning 10pm to 7am at the business); batches wait for 7am |
| Demo lines | 3 calls a day, 3 minutes each |
| Business lookups | 25 a day without a key, 300 a day with one |
| Setup codes | Expire after 24 hours |
For automation tools, see n8n, Zapier and Make. For an agent framework where a model decides the calls (OpenAI Agents SDK, Claude Agent SDK, Vercel AI SDK, LangChain), see agent SDKs.
Questions
Are the field names the same as the MCP tools?
Yes. The POST /calls body uses the same fields as place_call, and the responses match get_call.
Which endpoints work without a key?
POST /calls/plan, GET /businesses and GET /pricing. POST /calls to the demo line is free. Any other POST /calls without a key returns needs_setup with a one-time setup link, and the retry returns your key.
How does long polling work?
Add ?wait=45 to GET /calls/:id. The request returns early when there's something to act on (a question from the business, a new answer, or the final result) or when the wait runs out. Values above 50 are treated as 50.
Can I use this from n8n, Zapier or Make?
Yes. Any HTTP step can call it. See the automations page for examples.
Add CallForMe to your agent
One URL for any MCP agent. Connecting asks for nothing.
Install steps for your agentTry a free demo callPricing
On your phone? Email yourself the setup steps
One email with the link, for when you're at your computer. Nothing else.