name: phone-call description: "Place outbound AI phone calls via Bland AI: book reservations, make appointments, ask businesses questions, then report back the transcript and summary." license: MIT allowed-tools: "skill_run read_skill" metadata: gini: version: 1.0.0 author: Gini platforms: [macos, linux] requires: credentials: [BLAND_API_KEY] approval: [place-call]
Phone Call
Place outbound phone calls through Bland AI. An AI voice agent dials the number, follows your task prompt in a live conversation, and the full transcript plus a summary come back when the call ends. Everything runs through skill_run:
skill_run({ skill: "phone-call", script: "place-call",
args: { phoneNumber: "+15551234567", task: "..." } })
The runtime injects BLAND_API_KEY into the scripts — you never see or pass the key.
Workflow
- Gather the full task. Before anything else, collect: who to call (name + number), the goal, hard constraints (dates, times, party size, budget), fallbacks if the first ask isn't available, and the name to give if the callee asks who's calling. A vague task produces a bad call.
- Announce, then dial — the approval card is the confirmation. In one short message, state who you're calling (the exact number) and what the agent will say or ask, then call
place-callimmediately. Do not ask "should I go ahead?" in chat:place-callalways pauses on an Approve/Deny card, and approving that card is the user's go-ahead. A denied card ends the task; if the user sends a new message with changes, adjust per their feedback and submit a freshplace-call— never re-submit the identical call. - Place the call with
place-call. It returns{ ok, callId }. - Wait for the result with
check-call, passingwaitSeconds: 240— the script polls Bland every 10 seconds internally and returns as soon as the call completes (or when the budget runs out). If the result comes back withcompleted: false, callcheck-callagain with the same args and repeat untilcompletedistrue. - Report back the
summaryand the key points of thetranscript(quote relevant exchanges, don't dump the whole thing unless asked). Optional: for structured answers about the call (did they confirm? what time?), runanalyze-call.
If the user wants to abort a call in progress, run stop-call with the callId.
Scripts
place-call
skill_run({ skill: "phone-call", script: "place-call", args: {
phoneNumber: "+15551234567", // required, E.164
task: "...", // required, the call prompt
voice: "maya", // optional, Bland voice id/name
firstSentence: "...", // optional, exact opening line
waitForGreeting: true, // default true — wait for the callee to speak first
record: false, // default false — see Rules
maxDurationMinutes: 10, // default 10
language: "en-US" // optional
} })
Returns { ok, callId } or { ok: false, error }.
check-call
skill_run({ skill: "phone-call", script: "check-call", args: { callId: "...", waitSeconds: 240 } })
Returns { ok, callId, status, completed, answeredBy, callLengthMinutes, to, from, transcript, summary, recordingUrl, errorMessage }. waitSeconds (optional, default 0, max 240) makes the script wait for the call to finish, polling Bland every 10 seconds; if the budget runs out first it returns the latest status with completed: false — call again with the same args. Fields Bland hasn't populated yet are omitted; transcript and summary appear only after completed is true. callLengthMinutes is in minutes. answeredBy distinguishes human from voicemail.
stop-call
skill_run({ skill: "phone-call", script: "stop-call", args: { callId: "..." } })
Ends an in-progress call. Returns { ok, message }.
analyze-call
skill_run({ skill: "phone-call", script: "analyze-call", args: {
callId: "...", // required
goal: "Book a dinner reservation", // optional context
questions: [ // required, non-empty
["Did they confirm the reservation?", "boolean"],
["What time was booked?", "string"]
]
} })
Use after check-call reports completed: true when you need structured answers instead of reading the transcript — e.g. "Did they confirm the reservation?" → true. Each question is a [question, answerType] pair ("string", "boolean", "number"); a bare string question defaults to "string". Returns { ok, answers } with one answer per question, in order. Each analysis costs Bland credits, so prefer the transcript/summary for simple cases.
Background watching
The workflow above is synchronous — right for short errand calls where the user is waiting on the result. When the user doesn't want to wait, or the call may run long, hand the waiting to a scheduled job with a call-watch pre-run hook: the hook polls Bland with zero model turns while the call is in progress, and wakes the job's turn exactly once, when the call finishes.
- Place the call with
place-callas usual and note thecallId. - Create the watcher job:
create_job({
name: "call-watch <callId>",
intervalSeconds: 30,
oneShot: false, // must stay false — the silent in-progress ticks would auto-pause a one-shot job before the call finishes
timeoutSeconds: 120,
preRunHook: {
handlerId: "skill-script",
config: { skill: "phone-call", script: "call-watch", callId: "<callId>" }
},
prompt: "A phone call placed in the background has finished. The call result (status, summary, transcript) is in the fenced context items above — report the summary and the key transcript exchanges. Then find this job by name with list_jobs and delete_job it; the call is done and the watcher is no longer needed."
})
- Tell the user the call is underway and the result will arrive in the job's chat thread, then end your turn — do not poll with
check-call.
Notes:
- The report lands in the job's own chat thread (scheduled jobs deliberately do not post into the main chat).
call-watchis the job's hook script, not for directskill_runuse. A failed call (never answered, rejected) also wakes the turn, so the user always hears the outcome.
Writing a good task prompt
The task is the agent's script for the whole conversation. Include the goal, context the callee will ask about, constraints, and fallbacks. Example:
You are calling Luigi's Restaurant to book a dinner reservation. Book a table
for 4 people this Friday at 7:00 PM under the name Shelden. If 7:00 PM is not
available, try 7:30 PM, then 6:30 PM. If none of those work, ask what times
are available Friday evening and say you'll call back. If they ask for a
phone number, give +15551234567. Be polite and concise.
Use firstSentence when the opening line matters (e.g. "Hi, I'm calling on behalf of Shelden to ask about your store hours.").
Limitations
- Outbound only — this skill cannot answer incoming calls.
- Rate limit: about 1 call per 10 seconds to the same number.
- Transcript and summary are only available after the call completes —
check-callduring the call shows status only.
Rules
- Never ask for confirmation in chat — the Approve/Deny card on
place-callis the confirmation. State the number and task in your message, callplace-call, and let the user decide on the card. Calls reach real people and cannot be un-placed. A denial ends the task; if the user follows up with changes, submit a freshplace-callreflecting them — never re-submit the same unchanged call. - Never call emergency numbers (911, 112, 999, etc.) under any circumstances.
- Phone numbers must be E.164 format:
+15551234567. - Don't store third-party phone numbers in memory — only the user's own.
- Leave
record: falseunless the user explicitly asks for a recording — recording-consent laws vary by region. - Never include
BLAND_API_KEYin a reply or tool argument — the runtime injects it into the scripts.