agentcalls/SKILL.md
name: agentcalls
description: Run a business phone line through AgentCalls — AI voice agents on real phone numbers in 95+ countries. Use when the user wants to build or change a phone agent (receptionist, booking, lead qualification, support), get a phone number, make or receive calls, run an outbound calling campaign, read call transcripts and summaries, manage contacts, webhooks, integrations or billing through the AgentCalls MCP server or REST API. Covers the end-to-end workflow, the confirmation rules for money and irreversible actions, and how calls behave asynchronously.
AgentCalls
AgentCalls gives one organization AI voice agents that answer and place real phone calls. Through the MCP server (https://agentcalls.net/mcp, OAuth sign-in or API key) you can do everything the dashboard does: build and publish agents, get numbers, start and end calls, run campaigns, read transcripts and summaries, manage contacts, integrations, webhooks, outbound rules and billing.
Before anything else
- Call
get_started. It returns the organization's state (plan, balance, agents, numbers, integrations, recent calls) and an orderednextStepslist naming the tool for each step. Follow it rather than guessing. - Read
references/workflows.mdfor the step-by-step recipes andreferences/tools.mdfor every tool's arguments (generated from the server, always current). - Keep the user in the loop: summarise what you found, propose the next step, and ask before anything that costs money or cannot be undone (see "Confirmation rules").
The mental model
- Agent = a draft configuration (instructions, opening line, voice, hours, tools, analysis schema). Calls use the published version only; editing a draft changes nothing until
publish_agent. - Number = a phone number owned by the organization. Inbound calls on a number go to the agent assigned to it (
assign_number). Outbound calls (start_call, campaigns) need an active number that is not inbound-only. - Call = one conversation. Live statuses:
queued,ringing,in_progress. Final:ended,no_answer,busy,voicemail,failed. Summary, outcome, sentiment and structured data arrive about a minute after hang-up (get_call); the transcript grows live (get_transcript). - Campaign = a list of contacts dialled by one agent from one number inside calling hours, with retries. Rows move
queued → calling → done | failed | skipped. - Contact = one phone number with name, attributes, tags, notes, a memory summary the agent reads at the start of each call, and a do-not-call flag that blocks outbound calls.
- Money: a plan includes minutes and numbers per period; beyond that, calls draw from a prepaid wallet. Numbers cost a monthly fee.
get_usageshows all of it.
Confirmation rules (non-negotiable)
These tools spend the user's money, dial real phones, hang up on real people or cannot be undone. Each takes confirm: true; without it the server answers confirmation_required. Never set confirm: true unless the user said yes in this conversation to that specific action, after you showed them what it does and what it costs:
| Tool | Why it needs a yes |
|---|---|
buy_number | Monthly charge for the number |
request_number | Starts a registration that ends in a monthly charge |
release_number | Irreversible; callers can no longer reach the number |
start_call | Dials a real phone; per-minute cost |
end_call | Hangs up on a real person |
create_campaign with start: "now" or "at", set_campaign_status → running | Dials many real phones; cost scales with the list |
set_campaign_status → cancelled | Irreversible |
A campaign saved as a draft and set_campaign_status → paused need no confirmation. Links from get_topup_link and get_plan_link charge nothing by themselves (the user pays on the hosted page), but say what the link is before sharing it.
Working style
- Prefer one tool call that answers the question over several speculative ones. Lists are paginated with
cursor; stop when you have enough. - Use the ids tools return (
agentId,phoneNumberId,callId,campaignId,contactId); never invent ids. Phone numbers are E.164 (+14155550123). - Outputs are plain language (ended reasons, action labels); quote them as they are. Do not expose provider names or raw codes to the user.
- When a tool fails, read
error.codeanderror.message; most tell you the fix (publish the agent, allow the country, top up, acknowledge compliance in the dashboard).references/errors.mdlists them. - Dashboard links in results (
dashboardUrl) are for the user; offer them when a step needs a human (documents, compliance acknowledgement, payment). - Arabic organizations (
organization.locale = "ar") expect Arabic replies and Arabic agents; voices and templates exist in both languages.
Typical requests → where to start
| User says | Start with |
|---|---|
| "Set up a phone line / receptionist for my business" | get_started, then the Set up a phone line recipe |
| "Call X about Y" | get_contact (do-not-call?), list_agents (live), then start_call with a note after the yes |
| "What did callers want this week?" | list_calls with direction: "inbound" and since, then get_call for details |
| "Remind all customers that…" | Run a campaign recipe (draft first, review rejected rows, then start) |
| "Get me a number in …" | list_number_countries → instant: search_numbers + buy_number; request: request_number |
| "Connect my calendar / CRM" | list_integrations → get_integration_connect_link → update_agent tools |
| "Notify my system after each call" | create_webhook with call.summary.ready |
| "How much have I spent / what is my balance?" | get_usage, list_invoices |
The prompts set_up_phone_line, design_receptionist_agent, review_recent_calls, place_follow_up_call and run_campaign package the recipes above for clients that support MCP prompts.
agentcalls/references/workflows.md
AgentCalls workflows
Step-by-step recipes. Every step names the tool; arguments are in tools.md. "Ask" means stop and get the user's answer before continuing.
1. Set up a phone line from scratch
get_started. Note the plan: on the free trial, agents can be built and tested in the browser, but numbers and outbound calls need a paid plan (get_plan_link→ the user subscribes on the hosted page).- Pick a voice:
list_voiceswith the agent's language; propose two (one standard, one premium — premium costs more per minute, seeget_usage.rates) with theirpreviewUrl. Ask which. - Draft the agent: read the matching template resource (
agentcalls://templates/{receptionist|appointment_booking|lead_qualification|support}/{en|ar}), adapt the instructions to the business (name, services, hours, what to book, what never to promise, when to transfer). Ask for missing facts. Thencreate_agentwithtemplate,name,language,voiceId,instructions,firstMessage,businessHours, andtransferNumberif a human should take hand-offs. - Show the draft (the agent card renders it). Iterate with
update_agent(only the fields that change). When approved,publish_agent. - Hear it:
test_agentreturns the in-browser test link (free trial minutes, no number needed). If an outbound number exists, offer to call the user's own phone (start_callafter their yes, with a shortnote). - Get a number:
list_number_countries(or passcountry). Instant countries:search_numbers(US/CA acceptareaCode), show three candidates with the monthly price, and after the yesbuy_numberwithagentIdso the number routes to the agent at once. Request countries (UK, UAE, Saudi Arabia, Germany, …): collectfields(business name, registered address, contact, intended use), show them, and after the yesrequest_number; tell the user to upload the documents on the dashboard page in the result and that activation takes up to three business days (list_numbersshows open requests). - Finish:
assign_numberif the number is not yet routed; suggestcreate_webhook(call.summary.ready) if the user has a system to notify; showget_usage.
2. Change an agent
get_agentfirst; change only what the user asked withupdate_agent;publish_agentso calls pick it up. Mention that the previous published version keeps taking calls until publish.- Give the agent tools:
list_integrations(connected ones havestatus: "active"); pass action slugs as{ kind: "integration", slug }plus the built-ins (transfer_callneedstransferNumber,end_call,update_contact,lookup_contact) inupdate_agent.tools. Unconnected toolkits:get_integration_connect_linkfor the user to connect first. - Structured data after calls:
analysisSchema(JSON Schema object). Summary focus:summaryPrompt.
3. Place one call
get_contactby phone: ifdoNotCallis true, stop and tell the user.list_agents(status: "live") andlist_numbers(anactivenumber withinboundOnly: false). If several outbound numbers exist, passphoneNumberId.- Tell the user who will be called, by which agent, from which number, the goal and the per-minute rate (
get_usage.rates). Ask. start_callwithconfirm: true,contactNameif known,note= the goal, and anidempotencyKeywhen the request could be repeated. The call card shows live status, transcript and an End-call button; otherwise pollget_callevery 20–30 s untilliveis false, then once more a minute later for the summary.- Report outcome, sentiment, summary, duration and cost.
get_recording_urlgives a 10-minute link if the user wants to listen. - If the user wants the call stopped:
end_callafter their yes.
4. Review inbound and outbound activity
list_callswithdirection,since/until,status,agentId,contactorcampaignId; follownextCursorup to what is useful (≤ 200 calls for a summary).- Group by outcome and sentiment, count no-answer/busy/voicemail/failed, sum
durationSecandcostCents. Quotesummaryfor the calls that matter;get_transcriptonly when the user asks what was said. list_live_callsshows what is happening right now and the line usage.- Suggest concrete instruction improvements with evidence from calls.
5. Run an outbound campaign
get_organization:outbound.acknowledgedmust be true, otherwise the user has to tick the compliance acknowledgement in the dashboard (Settings → Outbound calling). CheckallowedCountriescovers the list;update_outbound_settingscan add countries after the user's yes.get_usage: enough included minutes or wallet for list size × expected minutes × rate;concurrency.limitmust be ≥ 2 (one line is reserved for inbound).- Collect the list (names + numbers, or CSV text with a header), the published agent and the caller-id number. Suggest a
note-style goal in the agent instructions if needed (update_agent). create_campaignwithstart: "draft". Reportacceptedand everyrejectedrow with its reason (invalid phone, duplicate, do-not-call, country not allowed); fix what the user wants.- After the explicit yes:
set_campaign_status→runningwithconfirm: true(orstart: "at"withstartAtfor later). The campaign card shows progress; otherwise pollget_campaignevery minute. set_campaign_status→pausedto stop dialling without confirmation;cancelledends it for good (needs the yes).- When completed:
get_campaign_results, summarise outcomes, list contacts that need a human follow-up.
6. Contacts, memory and do-not-call
list_contacts(searchby name, phone prefix or email;tag;doNotCall),get_contact(includesmemorySummaryandcallCount).upsert_contactcreates or updates (attributes merge, tags replace). Attributes are visible to the agent during calls.set_do_not_callblocks or unblocks outbound calls; it creates the contact if needed so the block sticks.
7. Webhooks and automation
create_webhookwithevents(call.started,call.ended,call.summary.ready,number.activated,wallet.low); the signingsecretis shown once.test_webhooksends a sample.list_webhooksshows recent deliveries.- The REST API (https://agentcalls.net/docs) offers the same objects with API keys for servers.
8. Billing
get_usage: plan, included minutes left, wallet, numbers allowance, lines, rates, period spend.get_topup_link(packs $25/$50/$100/$250) andget_plan_link(checkout for trial organizations, billing portal otherwise) return hosted pages for the user to pay on; nothing is charged by the tools.list_invoicesfor history (hosted PDF links).
agentcalls/references/tools.md
Tools reference
Generated from the server's tool spec (packages/mcp-server/src/spec.ts); do not edit by hand. Every tool returns JSON (structuredContent) and, on failure, { error: { code, message, details? } }. Lists accept limit (≤ 100) and cursor.
Getting started
get_started — Where am I? (setup status and next steps)
Scope org:read · read-only, card.
Call this first in a new conversation. Returns the organization's setup state (plan, balance, agents, numbers, integrations, recent calls) and an ordered list of next steps, each naming the tool to call and the dashboard page to open. Nothing is changed.
Returns: { organization, plan, balance, agents, numbers, integrations, recentCalls, onboarding, nextSteps: [{ step, why, tool, dashboardUrl }] }.
No arguments.
Example:
{}
Agents and voices
list_agents — List agents
Scope agents:read · read-only.
List the organization's voice agents with id, name, language, status, published version and voice tier. Use status: "live" to find agents that can take calls.
Returns: agents: array of { id, name, language, status, publishedVersion, voiceTier, firstMessage, createdAt }.
| Argument | Type | Required | Description |
|---|---|---|---|
status | "all" | "live" | "draft" | "archived" | default "all" | Filter by lifecycle state. live = has a published version. |
Example:
{
"status": "live"
}
get_agent — Get an agent
Scope agents:read · read-only, card.
Full configuration of one agent (draft): instructions, opening line, voice, consent, transfer number, hours, analysis schema, tools and publish state.
Returns: The agent object with every editable field, the voice as a list_voices id, and publishedVersion (0 = never published).
| Argument | Type | Required | Description |
|---|---|---|---|
agentId | string | yes | Agent id. |
Example:
{
"agentId": "6ad0bf49171339b53c8e"
}
create_agent — Create an agent
Scope agents:write · card.
Create a draft voice agent from a template, overriding any field you pass (instructions, opening line, voice, hours, tools…). The configuration is validated with the voice platform before it is saved. The draft takes no calls until publish_agent.
Returns: The created agent (same shape as get_agent). Validation failures come back as an error listing the issues.
| Argument | Type | Required | Description |
|---|---|---|---|
template | "receptionist" | "appointment_booking" | "lead_qualification" | "support" | default "receptionist" | Starting point for everything you do not set: instructions, opening line, consent text, summary prompt, analysis schema, built-in contact tools. |
name | string | no | Display name of the agent, e.g. "Front desk". |
language | "en" | "ar" | no | Language the agent speaks and understands: en or ar. Changes the default voice and consent text. |
voiceId | string | no | Voice id from list_voices (not a provider voice id). Defaults to the language's default voice. |
instructions | string | no | System instructions in plain language: who the agent is, what it may do and say, what it must never do. Markdown allowed. |
firstMessage | string | no | The agent's opening line when a call connects. |
consentAnnouncement | boolean | no | Play a recording announcement at the start of every call (default true; keep it on unless local law allows otherwise). |
consentText | string | no | The announcement text, in the agent's language. |
transferNumber | string | null | no | Human hand-off number for the transfer_call tool, or null to disable transfers. |
maxDurationSec | integer | no | Hard cap on call length in seconds (default 900). |
silenceTimeoutSec | integer | no | Seconds of silence before the agent ends the call (default 20). |
endCallPhrases | array of string | no | Phrases that end the call when the caller says them. |
voicemailDetection | boolean | no | Hang up (outbound) when voicemail answers instead of talking to the machine (default true). |
memoryEnabled | boolean | no | Give the agent the contact's memory summary and let it update contact attributes (default true). |
summaryPrompt | string | null | no | Extra guidance for the post-call summary, or null for the default. |
analysisSchema | object | null | no | JSON Schema (object) of the structured data to extract after each call, e.g. { type: "object", properties: { appointment_confirmed: { type: "boolean" } } }. Null disables extraction. |
businessHours | object | null | no | Opening hours the agent reads to callers; null means always open. |
tools | array of object | object | no | Replaces the agent's tool list: built-in call tools and actions from connected integrations the agent may use during calls. |
Example:
{
"template": "receptionist",
"name": "Front desk",
"language": "en",
"instructions": "You are Mia, the receptionist of Northwind Clinic…"
}
update_agent — Update an agent
Scope agents:write · card.
Change fields of an agent's draft. Only the fields you pass change. Published calls keep using the last published version until you call publish_agent again.
Returns: The updated draft.
| Argument | Type | Required | Description |
|---|---|---|---|
agentId | string | yes | Agent id. |
name | string | no | Display name of the agent, e.g. "Front desk". |
language | "en" | "ar" | no | Language the agent speaks and understands: en or ar. Changes the default voice and consent text. |
voiceId | string | no | Voice id from list_voices (not a provider voice id). Defaults to the language's default voice. |
instructions | string | no | System instructions in plain language: who the agent is, what it may do and say, what it must never do. Markdown allowed. |
firstMessage | string | no | The agent's opening line when a call connects. |
consentAnnouncement | boolean | no | Play a recording announcement at the start of every call (default true; keep it on unless local law allows otherwise). |
consentText | string | no | The announcement text, in the agent's language. |
transferNumber | string | null | no | Human hand-off number for the transfer_call tool, or null to disable transfers. |
maxDurationSec | integer | no | Hard cap on call length in seconds (default 900). |
silenceTimeoutSec | integer | no | Seconds of silence before the agent ends the call (default 20). |
endCallPhrases | array of string | no | Phrases that end the call when the caller says them. |
voicemailDetection | boolean | no | Hang up (outbound) when voicemail answers instead of talking to the machine (default true). |
memoryEnabled | boolean | no | Give the agent the contact's memory summary and let it update contact attributes (default true). |
summaryPrompt | string | null | no | Extra guidance for the post-call summary, or null for the default. |
analysisSchema | object | null | no | JSON Schema (object) of the structured data to extract after each call, e.g. { type: "object", properties: { appointment_confirmed: { type: "boolean" } } }. Null disables extraction. |
businessHours | object | null | no | Opening hours the agent reads to callers; null means always open. |
tools | array of object | object | no | Replaces the agent's tool list: built-in call tools and actions from connected integrations the agent may use during calls. |
Example:
{
"agentId": "6ad0bf49171339b53c8e",
"firstMessage": "Thanks for calling Northwind Clinic, this is Mia."
}
publish_agent — Publish an agent
Scope agents:write · card.
Validate the draft (locally and with the voice platform) and publish it as a new immutable version. From this moment inbound calls on assigned numbers and start_call use the new version.
Returns: { agentId, version, status: "live" }, or an error listing blocking issues.
| Argument | Type | Required | Description |
|---|---|---|---|
agentId | string | yes | Agent id. |
Example:
{
"agentId": "6ad0bf49171339b53c8e"
}
test_agent — Test an agent
Scope agents:read · read-only.
Dry-run check of an agent: validates the draft with the voice platform and reports warnings, then tells you how to hear it: a link to the in-browser test call (free trial minutes, no phone needed) and, when the organization has an outbound number, how to call the user's own phone with start_call.
Returns: { agentId, ok, issues, browserTestUrl, phoneTest: { possible, phoneNumberId, note } }.
| Argument | Type | Required | Description |
|---|---|---|---|
agentId | string | yes | Agent id. |
Example:
{
"agentId": "6ad0bf49171339b53c8e"
}
list_voices — List voices
Scope voices:read · read-only.
Browse the curated English and Arabic voice catalog (accents, gender, standard/premium tier, preview audio). Pass the returned id as voiceId to create_agent / update_agent.
Returns: voices: array of { id, name, language, gender, accent, tier, description, previewUrl }.
| Argument | Type | Required | Description |
|---|---|---|---|
language | "en" | "ar" | no | Only voices for this language. |
gender | "male" | "female" | no | |
tier | "standard" | "premium" | no | Premium voices cost more per minute (see get_usage for the plan's rates). |
accent | string | no | Substring match on the accent label, e.g. "gulf", "egyptian", "british". |
Example:
{
"language": "ar",
"gender": "female"
}
Phone numbers
list_number_countries — Countries where numbers are offered
Scope numbers:read · read-only.
The number catalogue: every country with the number types offered, whether each is bought instantly (search_numbers + buy_number) or on request (request_number, carrier registration handled by our team), the monthly price for this organization, inbound-only types and the documents a request needs.
Returns: countries: array of { code, name, dialCode, offers: [{ type, mode, monthlyPriceCents, inboundOnly, documents }] }, plus comingSoon.
| Argument | Type | Required | Description |
|---|---|---|---|
country | string | no | One country only (ISO 3166-1 alpha-2), e.g. AE. |
mode | "all" | "instant" | "request" | default "all" | instant = buy self-serve now; request = needs carrier registration (request_number). |
Example:
{
"mode": "instant"
}
list_numbers — List phone numbers
Scope numbers:read · read-only.
The organization's phone numbers with status, assigned agent and whether they can dial out. Use an active number with inboundOnly: false as phoneNumberId for start_call.
Returns: numbers: array of { id, phone, country, type, status, agentId, inboundOnly, monthlyPriceCents, createdAt }, plus requests (open number requests).
No arguments.
Example:
{}
get_number — Get a phone number
Scope numbers:read · read-only.
One number with its status, the agent answering it, pricing and the last provisioning error if any.
Returns: The number object.
| Argument | Type | Required | Description |
|---|---|---|---|
phoneNumberId | string | yes | Phone number id. |
Example:
{
"phoneNumberId": "68ee11aa00c1d2e3f405"
}
search_numbers — Search available numbers
Scope numbers:read · read-only, card.
Search the carrier inventory for numbers you could buy now, with the monthly price for this organization. Countries that need registration return no inventory and point to request_number instead. Nothing is purchased.
Returns: numbers: candidates { phone, locality, region }, plus monthlyPriceCents, includedInPlan, inboundOnly, regulatory (none | documents) and eligibility.
| Argument | Type | Required | Description |
|---|---|---|---|
country | string | yes | ISO 3166-1 alpha-2 country code; list_number_countries shows what is offered. |
type | "local" | "tollfree" | "mobile" | default "local" | local, tollfree or mobile. Not every type exists in every country; the result says which. |
contains | string | no | Digits the number should contain, e.g. "555" or a vanity pattern. |
areaCode | string | no | Area code (US and CA only). |
limit | integer | default 10 |
Example:
{
"country": "US",
"type": "local",
"areaCode": "415"
}
buy_number — Buy a phone number
Scope numbers:write · costs money, card.
Buy a number from search_numbers for this organization (monthly charge on the subscription unless the plan includes it). Requires confirm: true. Optionally assigns a published agent at once. Countries that need registration are not bought here: use request_number.
Returns: { purchased: true, number } when the number is active, or { purchased: false, reason, requirements?, dashboardUrl? }.
| Argument | Type | Required | Description |
|---|---|---|---|
phone | string | yes | A number returned by search_numbers, in E.164. |
country | string | yes | Country code used in the search. |
type | "local" | "tollfree" | "mobile" | default "local" | |
agentId | string | no | Assign this published agent to answer the number right after purchase. |
confirm | boolean | default false | Must be true. Buying a number starts a monthly charge. Ask the user before setting this. |
Example:
{
"phone": "+14155550123",
"country": "US",
"type": "local",
"confirm": true
}
request_number — Request a number that needs registration
Scope numbers:write · costs money.
For countries whose offer is request (United Kingdom, UAE, Saudi Arabia, Germany, …): files a number request with the business details; our team completes the carrier registration and activates the number (usually within three business days). Supporting documents are uploaded by the user in the dashboard (the result links to the form). Requires confirm: true.
Returns: { requestId, status: "requested", dashboardUrl, note }.
| Argument | Type | Required | Description |
|---|---|---|---|
country | string | yes | A country whose offer is request in list_number_countries (e.g. GB, AE, SA, DE). |
type | "local" | "tollfree" | "mobile" | default "local" | |
quantity | integer | default 1 | |
fields | object | yes | Business details the carrier registration needs. Documents (licence, ID, proof of address) are uploaded later in the dashboard; the result links to the form. |
confirm | boolean | default false | Must be true. A request starts a registration our team completes and ends with a monthly charge once the number is activated. Ask the user before setting this. |
Example:
{
"country": "GB",
"type": "local",
"fields": {
"businessName": "Northwind Ltd",
"address": {
"street": "1 High Street",
"city": "Reading",
"postalCode": "RG1 1AA",
"country": "GB"
},
"contactName": "Sara Ahmed",
"contactEmail": "sara@northwind.example",
"intendedUse": "Customer support line"
},
"confirm": true
}
assign_number — Assign a number to an agent
Scope numbers:write.
Set (or clear with null) the agent that answers inbound calls on a number. The agent must be published to actually answer.
Returns: The updated number.
| Argument | Type | Required | Description |
|---|---|---|---|
phoneNumberId | string | yes | Phone number id. |
agentId | string | null | yes | Agent that answers inbound calls on this number, or null to unassign. |
Example:
{
"phoneNumberId": "68ee11aa00c1d2e3f405",
"agentId": "6ad0bf49171339b53c8e"
}
release_number — Release a phone number
Scope numbers:write · irreversible.
Give a number back to the carrier and stop its monthly charge. Irreversible: callers get a disconnected tone and the number cannot be bought again for 30 days. Requires confirm: true.
Returns: { released: true, phone }.
| Argument | Type | Required | Description |
|---|---|---|---|
phoneNumberId | string | yes | Phone number id. |
confirm | boolean | default false | Must be true. Releasing a number is irreversible: callers can no longer reach it and the number cannot be bought back for 30 days. Ask the user before setting this. |
Example:
{
"phoneNumberId": "68ee11aa00c1d2e3f405",
"confirm": true
}
Calls (inbound and outbound)
start_call — Start an outbound call
Scope calls:write · costs money, card.
Dial a phone number with a published agent from one of the organization's numbers. note tells the agent what this call is about. Returns immediately with the call id while the phone rings; the conversation, summary and outcome arrive asynchronously (poll get_call, or receive the call.summary.ready webhook). Requires confirm: true.
Returns: { callId, status: "queued", note }. Errors: agent_not_published, number_not_active, number_inbound_only, payment_required, concurrency_limit, contact_do_not_call, country_not_allowed, call_start_failed.
| Argument | Type | Required | Description |
|---|---|---|---|
agentId | string | yes | A published agent (publishedVersion > 0). |
to | string | yes | Number to dial. |
phoneNumberId | string | no | Your caller-id number. Optional when the organization has exactly one active outbound number. |
contactName | string | no | Name passed to the agent when the number is not a known contact. |
note | string | no | Context for this call only, exposed to the agent as {{call_note}}, e.g. "Confirm Thursday 10:00; offer Friday if not possible". |
variables | object | no | Extra prompt variables for this call, available as {{campaign.<key>}}. |
idempotencyKey | string | no | Unique per intended call (e.g. an order id). Repeating a call with the same key returns the first call instead of dialing again. |
confirm | boolean | default false | Must be true. A call costs money and rings a real phone. Ask the user before setting this. |
Example:
{
"agentId": "6ad0bf49171339b53c8e",
"to": "+14155550123",
"contactName": "Sara Ahmed",
"note": "Confirm Thursday's 10:00 appointment",
"idempotencyKey": "order-8841-confirm",
"confirm": true
}
end_call — End a live call
Scope calls:write · card.
Hang up a call that is queued, ringing or in progress (for example one started with start_call that should not continue). Requires confirm: true. Idempotent: a call that already ended is reported as such and nothing changes. The transcript, summary and cost of the ended part still arrive asynchronously (get_call).
Returns: { callId, status, alreadyEnded, endedReason, note }. Errors: not_found (unknown id or another organization's call), call_not_ready (still being set up; retry in a few seconds), call_end_failed.
| Argument | Type | Required | Description |
|---|---|---|---|
callId | string | yes | A call that is queued, ringing or in progress (from start_call or list_calls). |
confirm | boolean | default false | Must be true. Ending a call hangs up on a real person. Ask the user before setting this. |
Example:
{
"callId": "68ef3b2c001a9d4e7f10",
"confirm": true
}
get_call — Get a call
Scope calls:read · read-only, card.
Status and results of a call: direction, numbers, timing, cost, summary, outcome, sentiment, structured data and the actions the agent took (plain-language labels). While the call is live this is the status to poll. The transcript is a separate tool (get_transcript).
Returns: The call object; summary/outcome are null until post-call analysis finishes (usually within a minute of hang-up). endedReason is a plain-language label.
| Argument | Type | Required | Description |
|---|---|---|---|
callId | string | yes | Call id. |
Example:
{
"callId": "68ef3b2c001a9d4e7f10"
}
get_transcript — Get a transcript
Scope calls:read · read-only.
The conversation of a call, as text or as structured turns. Grows while the call is in progress (live transcript).
Returns: { callId, status, turns } (json) or { callId, status, text }.
| Argument | Type | Required | Description |
|---|---|---|---|
callId | string | yes | Call id. |
format | "text" | "json" | default "text" | text = one line per turn ("agent: …"), json = array of { role, text, at }. |
Example:
{
"callId": "68ef3b2c001a9d4e7f10",
"format": "text"
}
get_recording_url — Get a recording link
Scope calls:read · read-only.
A signed, short-lived (10 minutes) URL to the call's audio recording. Fetch it again when it expires. Null when the call has no recording (in progress, failed, or deleted by retention).
Returns: { callId, url, expiresAt } or { callId, url: null, reason }.
| Argument | Type | Required | Description |
|---|---|---|---|
callId | string | yes | Call id. |
Example:
{
"callId": "68ef3b2c001a9d4e7f10"
}
list_calls — List calls
Scope calls:read · read-only.
Calls newest first, with optional filters by direction (inbound / outbound), agent, status, time window, contact or campaign. Paginate with cursor. Each row carries summary, outcome and sentiment once analysed.
Returns: { calls: Call[], nextCursor }.
| Argument | Type | Required | Description |
|---|---|---|---|
direction | "inbound" | "outbound" | "web" | no | Inbound = callers dialled your number; outbound = start_call or campaigns; web = browser test calls. |
agentId | string | no | Only calls handled by this agent. |
status | "queued" | "ringing" | "in_progress" | "ended" | "failed" | "no_answer" | "busy" | "voicemail" | no | |
since | string | no | Started at or after this instant. |
until | string | no | Started at or before this instant. |
contact | string | no | A contact id or an E.164 phone number. |
campaignId | string | no | Only calls placed by this campaign. |
limit | integer | default 25 | Page size, 1–100. |
cursor | string | no | Opaque nextCursor from the previous page. Omit for the first page. |
Example:
{
"direction": "inbound",
"status": "ended",
"since": "2026-10-01T00:00:00Z",
"limit": 20
}
list_live_calls — List live calls
Scope calls:read · read-only.
Calls that are queued, ringing or in progress right now, with how long each has been running and the line usage against the concurrency limit.
Returns: { calls: [{ id, direction, from, to, status, agentId, startedAt, elapsedSec }], inProgress, concurrencyLimit }.
No arguments.
Example:
{}
Outbound campaigns
list_campaigns — List campaigns
Scope campaigns:read · read-only.
Outbound campaigns with status and progress counters (queued, calling, done, failed, skipped).
Returns: campaigns: array of { id, name, status, agentId, phoneNumberId, total, queued, calling, done, failed, skipped, startAt, startedAt, completedAt }.
| Argument | Type | Required | Description |
|---|---|---|---|
status | "all" | "draft" | "scheduled" | "running" | "paused" | "completed" | "cancelled" | default "all" |
Example:
{
"status": "running"
}
get_campaign — Get a campaign
Scope campaigns:read · read-only, card.
One campaign with its settings (window, pacing, retries) and live progress. Poll it while running.
Returns: The campaign object plus progressPct and outcomes (reached / no answer / busy / voicemail / failed counts).
| Argument | Type | Required | Description |
|---|---|---|---|
campaignId | string | yes | Campaign id. |
Example:
{
"campaignId": "6ad129fa2c626a8bfa43"
}
get_campaign_results — Get campaign results
Scope campaigns:read · read-only.
Per-contact rows of a campaign: state, attempts, last call id and outcome, in file order. Paginate with cursor; use get_call / get_transcript for details of a row's call.
Returns: { rows: [{ id, phone, name, status, reason, attempts, lastCallId, lastCallStatus, outcome, nextAttemptAt }], nextCursor }.
| Argument | Type | Required | Description |
|---|---|---|---|
campaignId | string | yes | Campaign id. |
status | "queued" | "calling" | "done" | "failed" | "skipped" | no | Only rows in this state. |
limit | integer | default 25 | Page size, 1–100. |
cursor | string | no | Opaque nextCursor from the previous page. Omit for the first page. |
Example:
{
"campaignId": "6ad129fa2c626a8bfa43",
"status": "done",
"limit": 50
}
create_campaign — Create a campaign
Scope campaigns:write · costs money, card.
Create an outbound campaign from a contact list or CSV text. Rows with invalid phones, duplicates, do-not-call contacts or countries the organization may not dial are rejected and reported. start: "draft" only saves; now / at dial real phones and need confirm: true and the organization's outbound compliance acknowledgement (done once in the dashboard).
Returns: { campaignId, status, accepted, rejected: [{ line, phone, reason }], note }.
| Argument | Type | Required | Description |
|---|---|---|---|
name | string | yes | |
agentId | string | yes | A published agent. |
phoneNumberId | string | yes | Caller-id number (active, outbound-capable). |
contacts | array of object | no | The call list. Use this or csv. |
csv | string | no | Raw CSV text with a header row (phone or number column required; name and any other columns become variables). |
start | "draft" | "now" | "at" | default "draft" | draft saves without dialling; now starts immediately; at schedules startAt. |
startAt | string | no | Required when start = at. |
timezone | string | no | Organization time zone for startAt (default: the organization's). |
window | object | no | Calling hours, applied in each destination's local time (default: the organization's outbound window). |
maxConcurrent | integer | default 2 | Lines this campaign may use at once (one line is always kept free for inbound). |
maxAttempts | integer | default 2 | Attempts per contact when nobody answers or the line is busy. |
retryDelayMin | integer | default 120 | Minutes between attempts. |
voicemailDetection | boolean | default true | |
confirm | boolean | default false | Required (true) when start is now or at: a campaign dials real phones and spends money. Drafts need no confirmation. |
Example:
{
"name": "October renewals",
"agentId": "6ad0bf49171339b53c8e",
"phoneNumberId": "68ee11aa00c1d2e3f405",
"contacts": [
{
"phone": "+14155550123",
"name": "Sara Ahmed",
"variables": {
"plan": "Pro"
}
}
],
"start": "draft"
}
set_campaign_status — Start, pause, resume or cancel a campaign
Scope campaigns:write · costs money, card.
Move a campaign: draft/paused → running (dials phones, needs confirm), running → paused, any open state → cancelled (irreversible, needs confirm).
Returns: The campaign with its new status.
| Argument | Type | Required | Description |
|---|---|---|---|
campaignId | string | yes | Campaign id. |
status | "running" | "paused" | "cancelled" | yes | running = start or resume; paused = stop dialling, keep the queue; cancelled = stop for good. |
confirm | boolean | default false | Required (true) for running (dials phones, spends money) and cancelled (irreversible). Pausing needs no confirmation. |
Example:
{
"campaignId": "6ad129fa2c626a8bfa43",
"status": "running",
"confirm": true
}
Contacts
list_contacts — List contacts
Scope contacts:read · read-only.
Contacts newest first, filtered by free-text search (name, phone prefix, email), exact phone, tag or do-not-call flag. Paginate with cursor.
Returns: { contacts: Contact[], nextCursor }.
| Argument | Type | Required | Description |
|---|---|---|---|
search | string | no | Name, phone prefix or email. |
phone | string | no | Exact match (at most one result). |
tag | string | no | Contacts carrying this tag. |
doNotCall | boolean | no | |
limit | integer | default 25 | Page size, 1–100. |
cursor | string | no | Opaque nextCursor from the previous page. Omit for the first page. |
Example:
{
"search": "sara",
"limit": 20
}
get_contact — Get a contact
Scope contacts:read · read-only.
One contact by id or phone, including the memory summary the agent is given at the start of each call, attributes, tags, notes and call count.
Returns: The contact, or an error not_found.
| Argument | Type | Required | Description |
|---|---|---|---|
contactId | string | no | Contact id. |
phone | string | no | Phone number in E.164 format, e.g. +14155550123. |
Example:
{
"phone": "+14155550123"
}
upsert_contact — Create or update a contact
Scope contacts:write.
Create the contact for a phone number or update it when it exists. Attributes merge; tags replace.
Returns: { created: boolean, contact }.
| Argument | Type | Required | Description |
|---|---|---|---|
phone | string | yes | Identifies the contact: created when unknown, updated otherwise. |
name | string | null | no | |
email | string | null | no | |
attributes | object | no | Key/value strings merged into the stored attributes (set a key to "" to clear it). The agent can read and update them during calls. |
tags | array of string | no | Replaces the tag list. |
notes | string | no | Internal notes (not spoken by the agent). |
doNotCall | boolean | no |
Example:
{
"phone": "+14155550123",
"name": "Sara Ahmed",
"tags": [
"vip"
],
"attributes": {
"plan": "pro"
}
}
set_do_not_call — Set do-not-call
Scope contacts:write.
Block (or unblock) outbound calls to a contact. start_call and campaigns refuse blocked contacts.
Returns: The contact with the new flag.
| Argument | Type | Required | Description |
|---|---|---|---|
contactId | string | no | Contact id. |
phone | string | no | Creates the contact when it does not exist yet, so the flag sticks. |
doNotCall | boolean | default true | true blocks outbound calls to this contact; false lifts the block. |
Example:
{
"phone": "+14155550123",
"doNotCall": true
}
Balance and billing
get_usage — Balance, plan and usage
Scope usage:read · read-only, card.
Plan, included minutes left this period, prepaid wallet balance, number allowance, concurrency limit, per-minute overage rates and what this period cost so far. Check it before placing many calls or buying numbers.
Returns: { organization, plan, period, minutes, wallet, numbers, concurrency, rates, periodSpend }.
No arguments.
Example:
{}
list_invoices — List invoices
Scope billing:read · read-only.
Recent invoices of the subscription (amount, status, period, hosted PDF link).
Returns: invoices: array of { id, number, status, amountCents, currency, createdAt, periodStart, periodEnd, url }.
| Argument | Type | Required | Description |
|---|---|---|---|
limit | integer | default 12 |
Example:
{
"limit": 6
}
get_topup_link — Top-up link
Scope billing:read · read-only, card.
A checkout link that adds a prepaid pack to the wallet. Nothing is charged by this tool: the user pays on the hosted checkout page. Requires an active subscription.
Returns: { url, amountCents, expiresNote }.
| Argument | Type | Required | Description |
|---|---|---|---|
amountCents | integer | default 5000 | Prepaid pack in cents: 2500, 5000, 10000 or 25000 ($25, $50, $100, $250). |
Example:
{
"amountCents": 5000
}
get_plan_link — Plan change link
Scope billing:read · read-only, card.
A link to pick a plan (trial organizations: checkout for planId) or to change / cancel the plan and update the card (subscribed organizations: the billing portal). Nothing is charged by this tool.
Returns: { url, kind: "checkout" | "portal" }.
| Argument | Type | Required | Description |
|---|---|---|---|
planId | "starter" | "growth" | "scale" | no | Plan to subscribe to (trial organizations). Subscribed organizations get a billing-portal link to change plans instead; omit planId for the portal. |
Example:
{
"planId": "growth"
}
Integrations
list_integrations — List integrations
Scope integrations:read · read-only.
The verified tool catalogue (calendars, CRMs, helpdesks, spreadsheets, messaging) with each tool's connection status for this organization and the action slugs an agent can use during calls (pass them to update_agent tools).
Returns: integrations: array of { toolkit, name, category, status, connectedBy, actions: [{ slug, title, midCall }] }, plus connectedCount.
| Argument | Type | Required | Description |
|---|---|---|---|
connectedOnly | boolean | default false | Only tools the organization has connected. |
Example:
{
"connectedOnly": true
}
get_integration_connect_link — Connect-a-tool link
Scope integrations:read · read-only.
A link the user opens (signed in to the dashboard) to connect a tool with their own account; the sign-in happens on the tool's own site.
Returns: { url, toolkit, note }.
| Argument | Type | Required | Description |
|---|---|---|---|
toolkit | string | yes | Toolkit slug from list_integrations, e.g. googlecalendar, hubspot, slack. |
Example:
{
"toolkit": "googlecalendar"
}
Webhooks
list_webhooks — List webhooks
Scope webhooks:read · read-only.
Registered webhook endpoints with their events, active flag and the last deliveries (status, attempts).
Returns: webhooks: array of { id, url, events, active, recentDeliveries }.
No arguments.
Example:
{}
create_webhook — Create a webhook
Scope webhooks:write.
Register an HTTPS endpoint that receives signed events (call.started, call.ended, call.summary.ready, number.activated, wallet.low). The signing secret is returned once; store it to verify the X-AgentCalls-Signature header.
Returns: { id, url, events, secret } — the secret is never shown again.
| Argument | Type | Required | Description |
|---|---|---|---|
url | string | yes | HTTPS endpoint that receives signed JSON events. |
events | array of "call.started" | "call.ended" | "call.summary.ready" | "number.activated" | "wallet.low" | yes | Events to deliver. |
Example:
{
"url": "https://example.com/agentcalls",
"events": [
"call.summary.ready"
]
}
test_webhook — Send a test event
Scope webhooks:write.
POST a signed sample call.ended event to the endpoint and report the HTTP status and latency. Nothing is queued or retried.
Returns: { ok, status, durationMs, error? }.
| Argument | Type | Required | Description |
|---|---|---|---|
webhookId | string | yes | Webhook id. |
Example:
{
"webhookId": "68ef3c10000a1b2c3d4e"
}
Organization
get_organization — Get organization settings
Scope org:read · read-only.
Name, language, status, plan, home country, concurrency limit and the outbound calling rules (allowed countries, calling hours, time zone, consent text, compliance acknowledgement).
Returns: { id, name, locale, status, planId, homeCountry, concurrencyLimit, outbound: { allowedCountries, defaultCountries, window, timezone, consentText, acknowledged } }.
No arguments.
Example:
{}
update_outbound_settings — Update outbound calling rules
Scope org:write.
Change which countries outbound calls may dial, the default calling hours and time zone, and the outbound consent text. The compliance acknowledgement (TCPA / do-not-call responsibility) cannot be given here: a person must tick it in the dashboard before campaigns can start.
Returns: The outbound settings after the change.
| Argument | Type | Required | Description |
|---|---|---|---|
allowedCountries | array of string | no | ISO 3166-1 alpha-2 codes outbound calls may dial. Empty list = the default (the organization's own country plus the countries of its numbers). |
window | object | no | Default calling hours for campaigns, in each destination's local time. |
timezone | string | no | IANA time zone of the organization. |
consentText | string | no | Outbound consent / recording text agents read at the start of outbound calls (empty = the agent's own). |
Example:
{
"allowedCountries": [
"AE",
"SA",
"US"
],
"window": {
"start": "09:00",
"end": "20:00",
"days": [
0,
1,
2,
3,
4
]
},
"timezone": "Asia/Dubai"
}
Resources
agentcalls://skill— AgentCalls skill (SKILL.md): How to run a phone line through this server end to end: setup → agent → number → test call → inbound/outbound → summaries and webhooks → billing. Read it before a long task; it follows the Agent Skills format (agentskills.io).agentcalls://skill/{file}— Skill reference file: A reference file of the skill: workflows.md, tools.md (generated from this server's tool list), safety.md, errors.md.agentcalls://templates— Agent templates: The four agent templates (receptionist, appointment booking, lead qualification, support) in English and Arabic: instructions, opening line, consent text, analysis schema and built-in tools. Read one with agentcalls://templates/{template}/{language}.agentcalls://templates/{template}/{language}— One agent template: Full text of a template, e.g. agentcalls://templates/receptionist/ar.agentcalls://voices— Voice catalog: Every voice with language, gender, accent, tier and preview URL (same data as list_voices).agentcalls://voices/{language}— Voices for a language: Voices forenorar.agentcalls://countries— Number catalogue: Countries, number types, instant-vs-request mode and documents (same data as list_number_countries, without prices).
Prompts
use_agentcalls_skill— Load the AgentCalls skill: Inserts the skill (SKILL.md): the end-to-end workflow, the rules about money and confirmations, and where to look things up.set_up_phone_line— Set up a phone line from scratch: Guided setup: check the account, design and publish an agent, get a number (buy or request), test it, and hand over.design_receptionist_agent— Design a receptionist agent: Walks through choosing a voice, writing instructions, creating, publishing and assigning a number for a phone receptionist.review_recent_calls— Review recent calls: Pulls recent calls and summarizes outcomes, sentiment, failures and follow-ups.place_follow_up_call— Place a follow-up call: Checks the contact and do-not-call status, confirms with the user, then starts the call and reports the result.run_campaign— Run an outbound campaign: Collects the list, checks compliance and balance, creates the campaign as a draft, reviews rejected rows, then starts it with the user's go-ahead and reports progress.
agentcalls/references/safety.md
Safety, money and compliance
Money
- Calls cost per minute: included minutes first, then the prepaid wallet at
rates.overageStandardCentsPerMinuteoroveragePremiumCentsPerMinute(premium voices). Numbers costmonthlyPriceCentsper month unlessincludedInPlan.get_usageis the source of truth; quote it before expensive actions (campaigns, many calls, several numbers). - Low balance: when
wallet.lowBalanceis true, say so and offerget_topup_link; calls stop when minutes and wallet are exhausted (payment_required). - Never buy, request or release numbers, start calls or campaigns, or hang up without the user's explicit yes in this conversation; then and only then set
confirm: true. A yes for one call is not a yes for the next.
Phone etiquette and law
- The user is responsible for consent and recording law in each country; agents play a recording announcement by default (
consentAnnouncement). Do not switch it off unless the user asks and understands the implication. - Outbound calls respect the organization's allowed countries and calling hours (campaigns) and the do-not-call flag on contacts. Do not try to work around a
contact_do_not_call,country_not_allowedornot_acknowledgederror; explain it and offer the legitimate fix (ask the user to allow the country, or to tick the compliance acknowledgement in the dashboard). - Do not place calls to emergency numbers, premium-rate numbers or numbers the user has no relationship with. Decline requests that look like harassment, impersonation of authorities, or scams.
- Test calls: prefer the free in-browser test (
test_agent); a phone test to the user's own number is fine after their yes.
Data
- Transcripts, summaries and recordings are the organization's customer data. Quote only what the user needs; do not forward recordings or transcripts to third parties unless the user asks.
- Recording links expire after 10 minutes; fetch again rather than storing them.
- Webhook secrets and API keys are shown once; tell the user to store them, never repeat them later.
When things fail
internalor provider errors: retry once after a short wait, then tell the user with the time of the attempt.- A tool call never waits for a phone call; if a call seems stuck in
queued/ringingfor more than two minutes, report it and offerend_call.
agentcalls/references/errors.md
Error codes
Tool failures come back as isError: true with { error: { code, message, details? } }. Read message; it usually names the fix.
| Code | Meaning | What to do |
|---|---|---|
confirmation_required | A money, hang-up or irreversible tool was called without confirm: true. | Ask the user; call again with confirm: true only after a yes. |
forbidden | The connection or key lacks the scope. | OAuth: the client re-asks the user for the permission (step-up); API key: add the scope under Settings → Developers. |
invalid_request | Arguments are wrong or incomplete (details lists them). | Fix the arguments. |
not_found | Unknown id, or another organization's object. | Use ids from list tools. |
validation_failed | The agent draft has blocking issues (details). | update_agent the listed fields. |
validation_unavailable | The voice platform could not be reached for validation. | Retry in a minute. |
agent_not_published | The agent has no published version. | publish_agent. |
number_not_active / number_inbound_only | The caller-id number cannot dial out. | list_numbers; pick an active outbound number or buy one. |
payment_required | Suspended, cancelled, or no minutes and no wallet. | get_usage; offer get_topup_link / get_plan_link. |
concurrency_limit | Every line is busy. | Wait for a call to end (list_live_calls). |
contact_do_not_call | The contact is blocked. | Do not call; tell the user. |
country_not_allowed | Outbound to that country is not enabled. | update_outbound_settings after the user's yes, or the dashboard. |
not_acknowledged | Campaigns need the compliance acknowledgement. | A person ticks it in the dashboard (Settings → Outbound calling). |
call_start_failed / call_end_failed | The voice platform refused. | Retry once; then report. |
call_not_ready | The call is still being set up. | Retry end_call in a few seconds. |
not_eligible | Numbers need a paid plan / the subscription is past due. | get_plan_link. |
invalid_cursor | A pagination cursor is stale. | Start again without cursor. |
rate_limited (HTTP 429) | 60 requests per minute. | Wait Retry-After seconds. |
internal | Our side failed. | Retry; then report with the time. |
HTTP-level: 401 means the token expired or the key is invalid (the client re-authenticates); 403 with insufficient_scope means re-consent with more permissions.