place-outbound-calls
Skill0 starsChanged 58 days agoMITView on GitHub
Place, monitor, or troubleshoot an outbound ThunderPhone call. Use when someone asks about POST /v1/call, TCPA confirmation, insufficient balance, caller and recipient numbers, call variables, idempotency, polling status, transcripts, or when to use a campaign.
- Reads credentials
- Sends data out
In this repository21 other files it gives its agents.
- AGENTS.mdAGENTS.md
- add-knowledgeSkill
- build-tool-integrationSkill
- create-agentSkill
- embed-web-widgetSkill
- get-phone-numberSkill
- handle-inbound-callsSkill
- iterate-on-agentSkill
- migrate-from-blandSkill
- migrate-from-elevenlabsSkill
- migrate-from-retellSkill
- migrate-from-vapiSkill
- run-campaignSkill
- setup-api-keySkill
- setup-webhooksSkill
- test-agentSkill
- thunderphone-mcp-setupSkill
- thunderphone-prompt-builderSkill
- use-with-livekitSkill
- use-with-openai-realtimeSkill
- use-with-pipecatSkill
The file
We split this skill into the rules it gives an agent
7 rules. About 0 are common, 4 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 75
Poll and inspect: Use GET /v1/calls/{call_id} until terminal, then retrieve GET /v1/calls/{call_id}/transcript.
- Very specific: line 77
Switch to campaigns for volume: Use the run-campaign skill for contact imports, calling windows, pause/cancel controls, and aggregate …
- Distinctive: line 86
Call in the contact's local hours, and retry on another day at another time, not on a short loop.
- Very specific: line 91
Read answered_by on finished calls and watch GET /v1/phone-numbers/health.
- Distinctive: line 108
Follow place outbound calls, outbound calling laws, and [recording consent
- Distinctive: line 110
Never infer consent, disguise caller identity, or represent initiation as completion.
- Distinctive: line 111
Do not include sensitive data in variables unless the conversation requires it.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Place an outbound call
- Confirm authority and consent. Identify the recipient, purpose, local
time, applicable calling/recording rules, and the evidence supporting the
call. Do not infer consent. Check
GET /v1/outbound-tcpa-confirmation. Recording the confirmation requires an authenticated organization admin in the dashboard; an API key cannot self-attest around the gate. - Validate resources. The
from_numbermust support outbound calls. Use a deployedagent_id, supply a complete inlineconfig, or omit both to use the number'soutbound_agent; omission returns400if none is configured. Optionalmax_hold_secondsoverrides the hold timeout for this call. Check balance before a production batch. - Create an idempotency key for each intended call so a retry cannot create
a second call. Send per-call facts in
variables, not instructions. - Place one controlled call.
POST /v1/callrequiresfrom_numberandto_number; it acceptsagent_id,variables, andidempotency_key.
{
"from_number": "${FROM_NUMBER}",
"to_number": "${TO_NUMBER}",
"agent_id": 12,
"variables": {},
"max_hold_seconds": 120,
"idempotency_key": "${IDEMPOTENCY_KEY}"
}curl --fail-with-body -X POST https://api.thunderphone.com/v1/call \
-H "Authorization: Bearer $THUNDERPHONE_API_KEY" \
-H "Content-Type: application/json" \
-H "X-ThunderPhone-Client: skills/place-outbound-calls@1.0" \
--data "$(jq -n --arg from "$FROM_NUMBER" --arg to "$TO_NUMBER" --argjson agent "$AGENT_ID" --arg key "$IDEMPOTENCY_KEY" '{from_number:$from,to_number:$to,agent_id:$agent,idempotency_key:$key}')"import os, requests
payload = {"from_number": os.environ["FROM_NUMBER"], "to_number": os.environ["TO_NUMBER"],
"agent_id": int(os.environ["AGENT_ID"]), "idempotency_key": os.environ["IDEMPOTENCY_KEY"]}
r = requests.post("https://api.thunderphone.com/v1/call", json=payload,
headers={"Authorization": f"Bearer {os.environ['THUNDERPHONE_API_KEY']}",
"X-ThunderPhone-Client": "skills/place-outbound-calls@1.0"}, timeout=30)
r.raise_for_status()
print(r.json())const response = await fetch("https://api.thunderphone.com/v1/call", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.THUNDERPHONE_API_KEY}`,
"Content-Type": "application/json",
"X-ThunderPhone-Client": "skills/place-outbound-calls@1.0" },
body: JSON.stringify({ from_number: process.env.FROM_NUMBER, to_number: process.env.TO_NUMBER,
agent_id: Number(process.env.AGENT_ID), idempotency_key: process.env.IDEMPOTENCY_KEY }),
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());- Interpret acceptance correctly. A
201means initiation was accepted, not that a person answered or the conversation succeeded. A202can mean the provider outcome is not yet known. Preserve the returned call ID. - Poll and inspect. Use
GET /v1/calls/{call_id}until terminal, then retrieveGET /v1/calls/{call_id}/transcript. Use webhooks for scale. - Switch to campaigns for volume. Use the
run-campaignskill for contact imports, calling windows, pause/cancel controls, and aggregate statistics.
Deliverability checklist
Campaign pacing does not apply to POST /v1/call, so the caller's code does it:
- Choose a
from_numberwith the contact's area code and reuse it for that person. Keep each number well under 100 calls a day. - Call in the contact's local hours, and retry on another day at another time, not on a short loop.
- Send any heads-up text with
POST /v1/smsonly to people who agreed to it. - Set the agent to leave a voicemail rather than hang up, and give the number an inbound agent so callbacks reach someone.
- Read
answered_byon finished calls and watchGET /v1/phone-numbers/health. Retire a labeled number; never rotate fresh numbers to hide it.
See get outbound calls answered.
Errors
| Symptom | Cause | Fix |
403 names TCPA confirmation | Organization gate has not been completed by an admin | Complete the reviewed dashboard attestation; do not automate around it. |
402 insufficient balance | Organization cannot fund the call | Top up through an authorized billing flow, then retry with the same idempotency key only if no call exists. |
| Number rejected | Number lacks outbound capability or format is invalid | Read the number record and use E.164 formatting. |
| Duplicate calls | Retry used a new or missing idempotency key | Persist one key per intended call and reconcile the first result. |
Source and safety rules
- Follow place outbound calls, outbound calling laws, and recording consent laws.
- Confirm fields and statuses in outbound calls and calls.
- Never infer consent, disguise caller identity, or represent initiation as completion.
- Do not include sensitive data in variables unless the conversation requires it.
See references/outbound-readiness.md.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

