Submit a goal and a phone number; Dialfetch places an AI phone call (navigating IVRs and holds), then returns the outcome, notes, structured answers, a transcript, and a recording. Base URL https://app.dialfetch.com.
Every /api/v1/* endpoint requires an API key (create one on the API Keys page). Keys look like df_… and go in the Authorization header as a bearer token. Keep them server-side — a key can place billable calls.
These examples read the key from a DIALFETCH_API_KEY environment variable:
export DIALFETCH_API_KEY="df_0123456789abcdef0123456789abcdef" curl -s https://app.dialfetch.com/api/v1/credit \ -H "Authorization: Bearer $DIALFETCH_API_KEY"
POST /api/v1/calls. Only phone and goal are required. Pass an optional extract array to get typed, quoted answers back. Returns 201 with the new call id and status: "queued".
curl -s https://app.dialfetch.com/api/v1/calls \
-H "Authorization: Bearer $DIALFETCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+15551234567",
"goal": "Ask if my prescription is ready for pickup.",
"context": "Pickup for Jane Doe, DOB 04/12.",
"extract": [
{ "name": "ready", "type": "boolean" },
{ "name": "pickup_time", "type": "string" }
],
"max_duration_minutes": 15
}'
# -> {"id": "8b1f…", "status": "queued"}Each extract field takes a name (a-z / 0-9 / underscore) and a type of string, number, boolean, or date — up to 25 fields, with an optional description to guide the agent.
Every goal is screened before dialing. Harassment, threats, impersonating a specific real person, pretexting, unsolicited sales/robocalls, and pranks are rejected with 422:
{"error": "call rejected", "reason": "Goal not allowed (sales): …", "retriable": false}Placing a call also needs an active phone number (your caller ID) and enough credit for the call's estimated maximum cost:
422 {"error": "no_active_number"} // buy one first — see Numbers below
402 {"error": "insufficient_credit", "requiredCents": 500} // add credit — see Credit belowCalls to destinations outside 8:00–20:00 local time (inferred from the area code) are accepted but held in queued until the window opens. Optionally pass from_number_id to choose which of your active numbers is the caller ID (default: your oldest active number), and webhook_url to override your default result webhook.
GET /api/v1/calls/:id returns the full call. A call runs asynchronously — poll it (or set a webhook) until status reaches a terminal state (completed, failed, or canceled).
# Fetch once curl -s https://app.dialfetch.com/api/v1/calls/CALL_ID \ -H "Authorization: Bearer $DIALFETCH_API_KEY" # Poll until it finishes until curl -s https://app.dialfetch.com/api/v1/calls/CALL_ID \ -H "Authorization: Bearer $DIALFETCH_API_KEY" \ | grep -qE '"status": ?"(completed|failed|canceled)"'; do sleep 5 done
A completed call looks like:
{
"id": "8b1f…",
"phone": "+15551234567",
"goal": "Ask if my prescription is ready for pickup.",
"status": "completed", // queued | dispatching | dialing | ivr | on_hold |
// in_conversation | wrapping_up | completed | failed | canceled
"outcome": "accomplished", // accomplished | partial | failed | no_answer |
// refused | voicemail | ivr_dead_end
"confidence": 0.92,
"notes": ["Prescription is ready; pickup window ends at 6pm."],
"answers": {
"ready": {"value": true, "quote": "yes it's ready for pickup"},
"pickup_time": {"value": "before 6pm", "quote": "come by before six"}
},
"transcript": [{"speaker": "agent", "text": "…"}, {"speaker": "human", "text": "…"}],
"recording_url": "https://…",
"duration_seconds": 143,
"cost": {"total_usd": 0.18},
"events": [{"event": "queued", "at": "…"}, {"event": "dialing", "at": "…"}]
}GET /api/v1/calls — newest first, cursor-paginated. Filter by status; page with limit (1–100, default 25) and cursor (the previous page's next_cursor).
curl -s "https://app.dialfetch.com/api/v1/calls?status=completed&limit=25" \ -H "Authorization: Bearer $DIALFETCH_API_KEY"
DELETE /api/v1/calls/:id. A queued call is canceled immediately; an active call gets a cancel request sent to the voice engine and its final state arrives shortly after via the normal result flow.
curl -s -X DELETE https://app.dialfetch.com/api/v1/calls/CALL_ID \ -H "Authorization: Bearer $DIALFETCH_API_KEY"
Usage is billed in integer cents (USD) from a prepaid balance. At dispatch the estimated maximum cost is held; when the call finishes the hold is released and the actual cost is charged (base fee + per-minute for answered/voicemail calls; nothing for no-answer / failed / ivr_dead_end). You need at least one active number as your caller ID.
# Search for a number by area code
curl -s https://app.dialfetch.com/api/v1/numbers/search \
-H "Authorization: Bearer $DIALFETCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"areaCode": "415"}'
# Provision one (becomes a caller ID)
curl -s https://app.dialfetch.com/api/v1/numbers \
-H "Authorization: Bearer $DIALFETCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phoneNumber": "+14155550123"}'
# List your numbers / check credit / see prices
curl -s https://app.dialfetch.com/api/v1/numbers -H "Authorization: Bearer $DIALFETCH_API_KEY"
curl -s https://app.dialfetch.com/api/v1/credit -H "Authorization: Bearer $DIALFETCH_API_KEY"
curl -s https://app.dialfetch.com/api/v1/pricing -H "Authorization: Bearer $DIALFETCH_API_KEY"POST /api/v1/credit/deposit with {"amountCents": 2000} returns a Stripe checkoutUrl, or 501 if Stripe isn't configured (an admin funds you directly). Number endpoints require Twilio (501 otherwise). DELETE /api/v1/numbers/:id releases a number.
When a call finishes and a webhook_url is set (per call, or your default in settings), Dialfetch POSTs the full call object — the same shape as GET /api/v1/calls/:id. Delivery is retried 3 times with exponential backoff. Each delivery is signed: the x-dialfetch-signature header is the hex HMAC-SHA256 of the raw request body, keyed with the SHA-256 hash of your most recently created active API key. Always verify it before trusting the payload.
400 validation error {"error": "…"}
401 missing/invalid API key {"error": "unauthorized"}
402 not enough credit {"error": "insufficient_credit", "requiredCents": 500}
404 not found / not yours {"error": "not found"}
409 call already finished {"error": "call is already completed"}
422 guardrails rejected the goal {"error": "call rejected", "reason": "…", "retriable": false}
422 no active caller-ID number {"error": "no_active_number"}Dialfetch discloses on every call that it is an AI assistant calling on behalf of a client, and honors do-not-call requests automatically.