计划执行与检查节点
executing-plans
按既定实施计划完成工作,保持过程检查和反馈。
placecall【效率与协作】Place real outbound phone calls to US businesses through the PlaceCall API: book, ask, get quotes, then read the outcome and transcript.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
PlaceCall is a hosted API that places a real outbound phone call to a US business, has an AI voice
agent carry out a plain-English task on that call (an inquiry, a booking, a cancellation, a quote),
and returns a structured outcome, the transcript and the recording. The agent works through phone
menus and hold, and it can send the user a question in the middle of a call. When the user has no
number, the API can first suggest businesses to call and write the call brief for each one. This
skill teaches an agent to drive that API with curl. Adapted from the official PlaceCall skill:
https://github.com/voygr-tech/placecall
All requests go to https://api.voygr.tech over HTTPS with the header X-API-Key: $PLACECALL_API_KEY.
The user creates a key at https://api.voygr.tech/checkout (name and email; the key is emailed to
them) and puts it in the PLACECALL_API_KEY environment variable or the agent's secure settings.
Never ask for the key in chat, never print it, and never search the disk for credential files.
curl -s -H "X-API-Key: $PLACECALL_API_KEY" https://api.voygr.tech/users/me
# 200 {"customer_id":"...","credits_available":...,"max_concurrent_calls":...}
The call agent reads only the brief. Put every detail in it: what to ask, whose behalf you
call on (there is no separate caller-name field), names, dates, party size, a callback number,
and how to wrap up. Show the user the number and the brief and get a yes before dialling.
Generate a new UUID for Idempotency-Key for every call you intend to place, and keep it with
that call.
curl -s -X POST https://api.voygr.tech/calls \
-H "X-API-Key: $PLACECALL_API_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: 3f6c2a0e-0b8e-4c55-9a51-2d7d1f0b6c11" \
-d '{
"target_phone": "+15551234567",
"brief": "Call this sports bar and find out (1) whether they are showing the USA vs Netherlands match today and (2) whether a reservation is needed. Read the answers back to confirm, thank them, and end.",
"language": "en",
"ask_user_mode": "stream"
}'
# -> 201 {"call":{"call_id":"...","status":"dialing",...},...}
target_phone: E.164 (+1...). 911 and other N11 codes are refused with 422.brief: up to 4000 characters.language: auto (resolves to en) or one of en, es, fr, de, hi, ru, pt, ja,
it, nl, sr, tr, pl. Anything else returns 422 unsupported_language.ask_user_mode: always send "stream", so mid-call questions reach the events feed you poll.call.call_id. Some deployments answer 202 with status: "queued";
follow it the same way.For bookings, the optional structured path validates details before anything is dialled: send
"intent": "booking" with slots (target_phone, name, date as YYYY-MM-DD, time as
HH:MM, party_size, optional phone_to_dictate) instead of a brief. A gap comes back as
422 missing_slots with a suggested_question for each missing slot. This path returns a flat
call_id. Send either a brief or intent + slots, never both.
Poll; do not hold a long-lived curl -N stream open. Use the after_event_id query parameter.
ID=<call_id>; LAST=0; STOP=$(($(date +%s)+120))
while [ "$(date +%s)" -lt "$STOP" ]; do
OUT=$(curl -s --max-time 20 -H "X-API-Key: $PLACECALL_API_KEY" \
"https://api.voygr.tech/calls/$ID/events?after_event_id=$LAST")
[ -n "$OUT" ] && echo "$OUT"
N=$(printf '%s' "$OUT" | sed -n 's/^id: //p' | tail -1); [ -n "$N" ] && LAST=$N
printf '%s' "$OUT" | grep -q '^event: outcome' && { echo "### OUTCOME ###"; break; }
printf '%s' "$OUT" | grep -q '^event: ask_user' && { echo "### ASK_USER ###"; break; }
sleep 1
done
ask_user: read request_id and message from the data: JSON, get the answer from the user,
post it (Step 5), then poll again from the last event id. The call agent waits about 60 seconds
and then carries on without an answer. Events can repeat, so de-duplicate by request_id.outcome: the call has ended. Go to Step 6.curl -s -X POST https://api.voygr.tech/calls/$ID/answer \
-H "X-API-Key: $PLACECALL_API_KEY" -H "Content-Type: application/json" \
-d '{"request_id":"<from the ask_user data>","answer":"<answer, in the call language>"}'
{"delivered": true} means the agent heard it. delivered: false with
reason: "no_pending_request" means the question timed out or the call ended.
curl -s -H "X-API-Key: $PLACECALL_API_KEY" https://api.voygr.tech/calls/$ID
Keep polling every few seconds until outcome_type is non-null; it is the last field to be
written. Then read two separate fields:
result: goal_met, goal_partial, refused, goal_not_met, wrong_party, not_reached,
aborted.ended_by: callee_hangup, agent_hangup, dropped, budget_timeout, dial_failed,
customer_cancelled, system_error, compliance_stop.Either can be null, which means it was never established. outcome_type is deprecated; do not
branch on it. Report from outcome_summary and transcript_full (skip rows with role system),
not from a code alone. When has_recording is true, recording_url is a relative path: prefix
the base URL and send the same key.
curl -s -X POST https://api.voygr.tech/v1/places/suggest \
-H "X-API-Key: $PLACECALL_API_KEY" -H "Content-Type: application/json" \
-d '{"query": "florist in Chicago with fresh peonies in stock today",
"location_hint": "Wicker Park", "booking_name": "Alex", "callback_phone": "+13125550188"}'
The response holds up to six ranked cards in suggestions[]. Each card has phone_e164, a
ready-made call_brief and a suggestion_id. Show the cards, let the user pick, then place the
call with the card's phone_e164 as target_phone, its call_brief (read it first, edit if
needed) and its suggestion_id. The phone must equal the card's number, or the API returns
422 SUGGESTION_PHONE_MISMATCH. If degraded is true, tell the user why (degradation_reason).
User: Book a table for 4 tonight at 7:30 at +1 415 555 0142 under Alex Thompson.
Agent: Shows the number and this brief and asks for a yes:
"Call this restaurant and book a table for 4 tonight at 7:30 PM under the name Alex
Thompson. If they ask for a callback number, give 415 555 0199. Get an explicit
confirmation of the reservation before ending."
Places the call, polls events, then reports result goal_met, the confirmed time and
the relevant lines of the transcript.
Event: ask_user {"request_id":"...","message":"They only have 8:15 tonight. Take it?"}
Agent: Asks the user, posts "Yes, 8:15 is fine" to /calls/{id}/answer within the minute,
checks delivered: true, and keeps polling until the outcome event.
Idempotency-Key and body only when retrying the same call.POST /calls that timed out or returned 502/504 before checking
GET /calls?limit=20: the call may already have been placed, and a blind retry rings the
business twice.402 can
appear while the balance looks sufficient; GET /v1/usage reports call_credit_hold.
Answered place suggestions are billed too; refusals are not. Current rates:
https://api.voygr.tech/checkoutapi.voygr.tech must be in
sandbox.network.allowedDomains; a blocked request never reached the API and is not an outage.$PLACECALL_API_KEY; never echo it, paste it into chat or commit it.
A lost key is replaced at https://api.voygr.tech/recover.402 means out of credits: stop, tell the user the amount needed, and point them to
https://api.voygr.tech/checkout. Buying credits is the user's decision; never start a purchase.brief path it is nested at call.call_id; on the intent path it is
top-level call_id.status is completed but the outcome and transcript are null.
Solution: Keep polling GET /calls/{id} until outcome_type is non-null."ask_user_mode": "stream".409 on POST /calls.
Solution: idempotency_conflict means the key was reused with a different body;
idempotency_in_progress means wait detail.retry_after_seconds and retry with the same key;
a body listing active_call_ids means the concurrent-call limit was reached.