First month free with promo codeFirst month free with codeRedeem promoRedeem

Docs

ClawCall API and MCP docs

The ClawCall API places outbound calls to US +1 numbers with an English-speaking AI agent; the free trial covers 30 calls and 30 minutes, whichever lasts later. POST /call returns a call_id. Poll GET /call/:id until lifecycle is finalized, then read outcome, talk_seconds and the transcript when present. MCP hosts use OAuth and twelve tools instead of an API key.

Overview

ClawCall places real phone calls for you. Send a US number and a structured request, and it dials, works through phone menus, waits on hold, talks with whoever answers, and reports back. There are three ways in. Each one places the same calls, under the same account, plan, and limits.

  • Remote MCP

    For ChatGPT, Claude, and other AI hosts that support remote MCP with OAuth. In ChatGPT, install the official ClawCall plugin from ChatGPT's plugin directory. You sign in once. The host never sees an API key.

    Set up ChatGPT →
  • REST API

    For your own code. One request starts a call. You poll until it finishes, then read the result.

    Start the quickstart →
  • Agent skill

    For skill-aware agents such as Claude Code, Cursor, and OpenClaw. The skill drives the REST API for you.

    Install the skill →

How a call works

  1. You start a call with a destination and a structured request. ClawCall returns a call_id right away.
  2. ClawCall makes the call. It navigates menus, waits on hold, and talks with the person who answers. It says it is an AI if asked.
  3. You poll GET /call/:id. The lifecycle field moves through queued, dialing, answered, and finalized.
  4. You read the result. outcome says what happened on the phone network. The transcript field holds captured transcript evidence when present, which is how you judge whether the task got done.

Quickstart

Place a call and read the result in four steps. You need curl, a US number you can answer, and jq for the polling loop.

  1. Get an API key

    Sign in and create a key under Dashboard → API keys. Keys start with clawcall_sk_. To try ClawCall without an account, skip this step: your first call without a key starts the free trial and returns a new key in the api_key field. Save it and send it on every request after that.

    Shell
    export CLAWCALL_API_KEY="clawcall_sk_..."
  2. Start a call

    Send the destination and structured request as JSON. Replace +14155550123 with a number you control for your first test. Include every fact, detail, and question the recipient might need. Put commitments in authority, reference details in facts, and all questions in questions.

    curl
    curl -X POST https://api.clawcall.dev/call \
      -H "Content-Type: application/json" \
      -H "X-Api-Key: $CLAWCALL_API_KEY" \
      -d '{
        "to": "+14155550123",
        "recipient": "Ember Table",
        "on_behalf_of": "Jordan Lee",
        "goal": "Find dinner availability for two this Friday at 7 PM.",
        "authority": {"allow": []},
        "questions": ["Is a table for two available Friday at 7 PM?", "What are the deposit and cancellation terms?"]
      }'

    The response comes back as soon as the call is queued. It is not the result of the call.

    201 Created
    {
      "call_id": "ba645d75-3c1e-4f0a-9b52-6f1d2e7a8c90",
      "status": "queued",
      "message": "Call initiated. Poll GET /call/:id for status and transcript."
    }
  3. Poll until the call is finalized

    Save the call_id as CALL_ID and check it every 3 seconds. The call is over when lifecycle is finalized.

    CALL_ID="ba645d75-3c1e-4f0a-9b52-6f1d2e7a8c90"  # from step 2
    
    while true; do
      CALL=$(curl -sf https://api.clawcall.dev/call/$CALL_ID -H "X-Api-Key: $CLAWCALL_API_KEY") \
        || { echo "Request failed" >&2; break; }
      [ "$(echo "$CALL" | jq -r .lifecycle)" = "finalized" ] && break
      sleep 3
    done
    
    echo "$CALL" | jq '{outcome, talk_seconds, transcript}'
  4. Read the result

    outcome tells you whether the phone network connected the call. It does not tell you whether the restaurant booked the table. For that, check the nullable transcript, which holds captured transcript evidence when present.

    GET /call/:id, trimmed
    {
      "id": "ba645d75-3c1e-4f0a-9b52-6f1d2e7a8c90",
      "lifecycle": "finalized",
      "outcome": "answered",
      "talk_seconds": 94,
      "transcript": [
        { "role": "user", "text": "Ember Table, how can I help?", ... },
        { "role": "assistant", "text": "Hi, I am calling for Jordan Lee about availability...", ... }
      ],
      ...
    }

    Every field is described in The call object.

Authentication

Send your key in the X-Api-Key header on every request. Keep it on a server. Never put it in a browser bundle, a public repository, or a prompt someone else can read.

KeyHow it works
Account keyCreated in the dashboard, or a trial key you linked. Uses your account's plan or trial and unlocks account endpoints such as /me/calls and /me/call-preferences.
Trial keyIssued automatically by your first POST /call without a key, in the api_key field and the X-ClawCall-Api-Key header. It draws on a free trial tracked by the network address that created it.

Keep a trial key when you sign up

Open https://clawcall.dev/sign-up?token=clawcall_sk_... with your trial key, or /sign-in?token=... if you already have an account. ClawCall links the key to your account, so the same key now uses your plan. When a trial runs out, the trial_exhausted error carries this link in action.sign_in_url.

Manage keys in the dashboard. An unknown or revoked key is treated as no key at all. Account endpoints such as /me/calls then return 401 auth_required. POST /call falls back to the trial key already issued to your network address, and returns 401 invalid_api_key only when there is none. Remote MCP does not use API keys at all: the AI host gets revocable OAuth tokens instead.

Place a call

POST /call starts one outbound call. Send a JSON body with a Content-Type: application/json header. Required fields are to, recipient, on_behalf_of, goal, and authority.allow. Include all known facts and questions; more information makes calls better. Objects are strict and cannot mix structured and legacy fields. The 100 KB JSON body limit is the only size cap.

FieldDescription
tostring RequiredUS E.164 destination, such as +14155550123, without spaces.
recipientstring RequiredBusiness, department, location, or person expected to answer.
on_behalf_ofstring RequiredPerson or organization represented; this grants no account access.
goalstring RequiredThe full outcome wanted from this call.
authorityobject RequiredRequired allow list of permitted commitments; [] means ask and relay only. Optional limits lists budgets, times, and other boundaries.
factsarrayEvery known detail as {label, value} strings, including callback numbers, account references, known menu paths, and prior-call results.
questionsstring[]Every question the user wants answered, including alternatives and follow-up details.
handoffobjectPresence enables loop-in. The assistant rings your loop-in contacts, or your verified account phone when none are set. Optional handoff.when adds your trigger; requests for the user or a real person also trigger it. warm_greeting overrides the saved setting.
voicemailobjectUse {kind: "end"} (default) to end the call without leaving a message (the recording notice at pickup still plays), or {kind: "leave_message", message: "..."} with only the words to convey, including authorized callback details. The agent leaves this message on the voicemail, so write it as the message itself, with exact names, numbers, and dates. Put no instructions to the agent inside message.
assistant_namestringA plain assistant name, such as Alex.
voicestringeric, jessica, sarah, or chris. Omission uses the saved voice.

Optional lists must be non-empty when supplied, except authority.allow, which may be empty. Existing to plus task requests still work with legacy validation and settings. Send one format per request.

Response

201 Created with call_id, status: "queued", and a message. A trial call made without a key also returns api_key. Account responses carry X-ClawCall-Balance-Seconds, X-ClawCall-Balance-Minutes, and X-ClawCall-Tier headers. On unlimited plans the balance headers read unlimited.

End a call

POST /call/:id/hangup stops a queued or live call. It is safe to repeat: a call that already ended returns success: true with the message Call already ended.

curl -X POST https://api.clawcall.dev/call/$CALL_ID/hangup \
  -H "X-Api-Key: $CLAWCALL_API_KEY"

The call object

GET /call/:id returns the call. You can read calls placed by your account, and calls placed from the same network address. Trial calls have no account, so they are matched only by the network address that placed them. Another account’s call returns 403 not_authorized, and an unknown ID returns 404 not_found. POST /call/:id/hangup follows the same rules.

Lifecycle

ValueMeaning
queuedAccepted and waiting for a line.
dialingRinging the destination.
answeredConnected. The conversation is in progress.
finalizedOver. The terminal fields below are now present.

Fields

FieldDescription
idstringThe call ID, the same value as call_id.
directionstringoutbound or inbound.
handling_modestringagent for calls the AI handled. Inbound passthrough calls read passthrough.
requestobjectFor structured calls, the exact submitted request in GET /call/:id and /me/calls. Customer webhooks omit it, like task; fetch GET /call/:id.
taskstringThe task text the phone agent received: the structured request written out, or the original legacy task.
voice, personality, greetingstring or nullThe effective voice; personality and greeting are null for structured calls.
numbersobjectto, from (the caller ID used), and bridge_from (the line used for a handoff, or null).
timestampsobjectqueued_at, dialing_at, answered_at, finalized_at. Each is an ISO time or null.
transcriptarray or nullTurns of { role, text, timestamp, phase? }. Null until the first turn. It grows while the call runs, so you can show progress. role names the speaker, such as assistant for ClawCall and user for the other party. phase marks turns from a handoff.
outcomestringFinalized only. The phone-network result. See the table below.
outcome_detailobjectFinalized only. reason has code, title, message, and retryable. Show reason.message to people and use reason.retryable to decide on a retry. The carrier fields provider_error_code, hangup_cause, sip_hangup_cause, and hangup_source are nullable.
talk_secondsnumber or nullFinalized only. Seconds from answer to hang-up. Null if nobody answered.
recording_urlstring or nullFinalized only. A temporary recording URL when available. See Recordings.
_metaobjectFinalized only. Your remaining trial or plan. balance_seconds: null with unlimited: true means an unlimited plan, not zero.

Example

GET /call/:id, finalized
{
  "id": "ba645d75-3c1e-4f0a-9b52-6f1d2e7a8c90",
  "direction": "outbound",
  "handling_mode": "agent",
  "task": "Recipient: Ember Table\nOn behalf of: Jordan Lee\nGoal: Find dinner availability for two this Friday at 7 PM.\nMay commit to: nothing: ask and pass on messages only\nLimits: none supplied\nQuestions:\nq1: Is a table for two available Friday at 7 PM?\nq2: What are the deposit and cancellation terms?\nHandoff: disabled\nVoicemail: end",
  "request": {
    "to": "+14155550123",
    "recipient": "Ember Table",
    "on_behalf_of": "Jordan Lee",
    "goal": "Find dinner availability for two this Friday at 7 PM.",
    "authority": {"allow": []},
    "questions": ["Is a table for two available Friday at 7 PM?", "What are the deposit and cancellation terms?"]
  },
  "voice": "eric",
  "personality": null,
  "greeting": null,
  "numbers": {
    "to": "+14155550123",
    "from": "+14155550100",
    "bridge_from": null
  },
  "lifecycle": "finalized",
  "outcome": "answered",
  "outcome_detail": {
    "reason": {
      "code": "answered",
      "title": "Completed",
      "message": "The call connected successfully.",
      "retryable": false
    },
    "provider_error_code": null,
    "hangup_cause": "normal_clearing",
    "sip_hangup_cause": "200",
    "hangup_source": "callee"
  },
  "talk_seconds": 94,
  "timestamps": {
    "queued_at": "2026-09-28T17:00:00.000Z",
    "dialing_at": "2026-09-28T17:00:01.000Z",
    "answered_at": "2026-09-28T17:00:08.000Z",
    "finalized_at": "2026-09-28T17:01:42.000Z"
  },
  "transcript": [
    {
      "role": "user",
      "text": "Ember Table, how can I help?",
      "timestamp": "2026-09-28T17:00:09.000Z"
    },
    {
      "role": "assistant",
      "text": "Hi, I am calling for Jordan Lee. Is a table for two available Friday at 7 PM?",
      "timestamp": "2026-09-28T17:00:12.000Z"
    }
  ],
  "recording_url": "https://...",
  "_meta": {
    "balance_seconds": null,
    "unlimited": true,
    "plan": { "id": "unlimited", "status": "active" }
  }
}

Outcomes

outcome is one of ten values. A network outcome does not prove task success: answered only means someone or something picked up. Voicemail is not an outcome. For structured calls, ClawCall leaves a voicemail only with voicemail.kind set to leave_message, and you learn that from the transcript.

ValueMeaning
answeredThe call connected. Whether the task got done is in the transcript evidence actually captured.
no_answerNobody picked up before the ring timeout.
busyThe line was busy.
rejectedThe other side declined the call.
invalid_numberThe number is not in service or does not exist.
unreachableThe number exists but could not be reached right now.
network_errorA carrier network problem. Usually worth one retry.
cancelledYou or your agent ended the call before it finished.
system_errorClawCall hit an internal error during the call.
failedThe call failed for another reason. Check outcome_detail.

List recent calls

GET /me/calls lists your account’s calls. It needs an account key. Inbound history also needs Unlimited Reserve Plus and an active reserved number, or a number on your own Twilio account connected to ClawCall.

ParameterDescription
limitintegerDefault 25. Values above 200 are capped at 200.
directionstringoutbound or inbound.
sinceISO timeOnly calls that ended at or after this time.
  • Calls are newest first. With since or direction=inbound, only finalized calls are listed, ordered by when they ended.
  • The response is { calls, recordingWindowMinutes }. Each row is a call object without _meta, so it carries captured transcript evidence when present.
  • Rows add recording_available_until and recording_expired. Here recording_url turns null once the 10-minute window closes.
  • An invalid parameter returns 400 invalid_query.
curl
curl "https://api.clawcall.dev/me/calls?limit=25&direction=outbound" \
  -H "X-Api-Key: $CLAWCALL_API_KEY"

Live handoff

Include handoff when you may need to talk yourself, for example once a claims agent is on the line. The agent gets a tool to bring you in. It does not dial you right away.

curl
curl -X POST https://api.clawcall.dev/call \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $CLAWCALL_API_KEY" \
  -d '{
    "to": "+14155550123",
    "recipient": "My insurer, claims department",
    "on_behalf_of": "Jordan Lee",
    "goal": "Find the status of claim 88213",
    "authority": {"allow": []},
    "facts": [{"label": "Claim number", "value": "88213"}],
    "questions": ["What is the status and next step?"],
    "handoff": {"when": "Once a claims agent answers"}
  }'
  1. The agent decides it is time. It puts the other party on hold and calls your verified account phone on a separate line, or your loop-in contacts in order when you have them.
  2. You get a private briefing about who is waiting and why, and it asks whether you want to join. With handoff.warm_greeting: false it skips the briefing and connects as soon as your phone answers, so a voicemail or screening service could pick up instead.
  3. You join or decline. If you join, both lines are bridged and the agent drops off. If you decline or miss the call, the agent goes back to the original call and can pass on your message.
ValueDestination
{}Your loop-in contacts, or your verified account phone when none are set. Optional handoff.when adds a trigger; warm_greeting controls the private briefing, and each loop-in contact uses its own.
omittedLoop-in is disabled.

After you join, recording and transcription follow your Record bridged conversations setting (post_bridge_capture), so captured evidence may end at the bridge. Loop-in errors return before any call is placed: invalid_call_request (400 for malformed handoff), account_phone_unavailable (422, no single verified phone), account_phone_lookup_unavailable (503, try again shortly), and invalid_loop_in_destination (400, your phone is the destination or a ClawCall number).

In the dashboard assistant, ask for it in plain words, such as "Join me when a human is ready."

Recordings

A finalized call can include recording_url, a temporary recording URL when available. GET /call/:id/recording redirects (302) to the same temporary URL, or returns 404 recording_not_available when the call has none. On this route, a call you cannot access also returns 404 not_found rather than 403, so it never reveals that someone else’s call exists.

curl
curl -sI https://api.clawcall.dev/call/$CALL_ID/recording \
  -H "X-Api-Key: $CLAWCALL_API_KEY"
# 302 Found, Location: the temporary recording URL

In /me/calls, recording_available_until marks the end of the 10-minute window and recording_expired turns true once it passes.

Call settings

Saved settings apply to every call that does not override them. GET, PUT, and DELETE /me/call-preferences need an account key.

FieldDescription
voicestringjessica, sarah, chris, or eric. Other values return 400 invalid_preferences.
personalitystring or nullUsed for legacy and inbound calls only; structured calls use assistant_name.
greetingstring or nullThe opening line for legacy outbound calls; ignored for structured calls.
warm_greetingbooleanThe loop-in briefing, for both directions. Starts as true.
post_bridge_capturebooleanRecord bridged conversations. Keeps recording and transcribing after you join. Starts as true.
inboundobject or nullThe inbound assistant. Null unless you have Unlimited Reserve Plus and an active reserved number, or a number on your own Twilio account connected to ClawCall.
curl
curl -X PUT https://api.clawcall.dev/me/call-preferences \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $CLAWCALL_API_KEY" \
  -d '{
    "voice": "sarah",
    "personality": "Alex, a calm assistant calling on behalf of Jordan Lee.",
    "greeting": "Hi, this is Alex calling on behalf of Jordan Lee.",
    "warm_greeting": true
  }'

Reserved numbers and inbound calls

A reserved number gives your account a private caller ID for outbound calls. Choose one under Dashboard → Numbers. If your plan includes a reserved number but none is active, the call fails with 409 reserved_number_unavailable instead of switching to a shared caller ID. A reserved number handles one call at a time, so a second call returns 409 reserved_number_busy.

Unlimited Reserve Plus numbers can also receive inbound calls. ClawCall answers first as your assistant using your inbound profile. Configure instructions, voice, personality, and optional account-based loop-in from the dashboard or API. An inbound block can carry instructions, greeting, loop_in_user, passthrough_numbers, and knowledge_base, up to 20,000 characters of facts the assistant can tell callers. Each omitted field keeps its saved value, and a null knowledge_base clears it. On the first save, an omitted instructions or greeting takes the default text. Sending loop_in_user replaces a legacy handoff_number. The saved profile greeting does not replace or append to the fixed recording-disclosure opening.

passthrough_numbers lists up to 100 US callers who ring your verified phone directly with their own caller ID, skipping the assistant and the recording. An empty array clears the list and omitting it keeps the saved list. Send inbound: null to remove the inbound profile.

curl
curl -X PUT https://api.clawcall.dev/me/call-preferences \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $CLAWCALL_API_KEY" \
  -d '{
    "inbound": {
      "instructions": "Take the caller's name, reason, and callback number.",
      "greeting": "Hi, this is Jordan's assistant. How can I help?",
      "loop_in_user": true,
      "passthrough_numbers": ["+14155550199"]
    }
  }'

Webhooks

Instead of polling, ClawCall can send a signed POST to your HTTPS endpoint when a call starts or ends. Events are notifications: fetch the transcript and recording from the API paths in data.links. Each account has one endpoint and one signing secret. Set them on the Webhooks page or with an account key. If the API answers 404 webhooks_unavailable, webhooks are not turned on yet, so keep polling.

EventSent when
call.startedAn inbound call comes in. data.call.numbers.from is the caller.
call.completedAny call, inbound or outbound, has ended, usually within 10 seconds of hang-up. On an answered call it waits up to 30 seconds for the recording, then adds data.links.recording; fetch it right away, because GET /call/:id/recording redirects to a link that expires about 10 minutes after the call ends. Fetch the transcript, when present, from GET /call/:id. It carries data.summary, a short AI summary, and data.caller, the other party's stated name and reason (a field is null when it wasn't said). Both are null when nobody spoke or no summary could be made.

data.call has the fields of the call object without the transcript, task, voice settings, or recording_url. data.links paths are relative to https://api.clawcall.dev. Outbound calls send no call.started, so match call.completed to the call_id that POST /call gave you. Only calls made after you first set the endpoint are sent.

call.completed
{
  "id": "evt_ba645d75-3c1e-4f0a-9b52-6f1d2e7a8c90_completed",
  "type": "call.completed",
  "created_at": "2026-10-11T18:04:12.300Z",
  "data": {
    "call": { "id": "ba645d75-...", "direction": "inbound", "lifecycle": "finalized", "outcome": "answered", "talk_seconds": 74, ... },
    "links": { "call": "/call/ba645d75-...", "recording": "/call/ba645d75-.../recording" },
    "summary": "Dana from Acme Plumbing called about an unpaid invoice and asked for a callback.",
    "caller": { "name": "Dana", "reason": "Unpaid invoice" }
  }
}

Verify each request

Requests follow the Standard Webhooks format, so its libraries can verify them. webhook-signature is v1, plus a base64 HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body}, keyed with the decoded part of the secret after whsec_. Check it against the raw body and reject timestamps more than 5 minutes old.

Node.js
import crypto from "node:crypto";

// rawBody: the exact request bytes, before JSON parsing. secret: "whsec_...".
function verify(rawBody, headers, secret) {
  const key = Buffer.from(secret.slice("whsec_".length), "base64");
  const signed = `${headers["webhook-id"]}.${headers["webhook-timestamp"]}.${rawBody}`;
  const expected = crypto.createHmac("sha256", key).update(signed).digest("base64");
  const fresh = Math.abs(Date.now() / 1000 - Number(headers["webhook-timestamp"])) < 300;
  return fresh && headers["webhook-signature"].split(" ").some((sig) => {
    const value = sig.slice(sig.indexOf(",") + 1);
    return value.length === expected.length && crypto.timingSafeEqual(Buffer.from(value), Buffer.from(expected));
  });
}

Custom headers

To check a credential of your own, such as an Authorization header, add up to 5 custom headers. ClawCall sends them with every delivery and test event, stores the values encrypted, and only shows a masked value_preview afterwards. ClawCall's own Content-Type, User-Agent, and webhook-* headers always win, and names like Host or Content-Length are refused. Moving the endpoint to another origin (scheme, host, or port) removes the saved headers, so a credential never follows it to another host.

Delivery and retries

  • Answer with any 2xx within 10 seconds. Anything else is retried after 30 s, 2 min, 10 min, 30 min, 1 h, 2 h, 4 h, and 6 h. 410 Gone stops retries for that event.
  • Retries keep the same id, so deduplicate on it. For an inbound call, call.completed waits until call.started is delivered or given up.
  • The endpoint must resolve to a public internet address. Redirects are not followed.

Set up with the API

RequestEffect
GET /me/webhookThe endpoint, secret, events, masked custom headers, and headers_available, or null.
PUT /me/webhookSets or changes the URL. The first save creates the secret; changing the URL keeps it. An optional headers object replaces every custom header.
PUT /me/webhook/headers/:nameAdds one custom header or replaces it. Body: { "value": "..." }.
DELETE /me/webhook/headers/:nameRemoves one custom header.
DELETE /me/webhookRemoves the endpoint, secret, and custom headers.
POST /me/webhook/rotate-secretReplaces the secret. The old one stops working immediately.
POST /me/webhook/testSends a signed webhook.test event and returns your endpoint's answer.
GET /me/webhook/deliveriesRecent calls with each event's status, attempts, and last error.
POST /me/webhook/deliveries/:call_id/resendSends one event again. Body: { "event": "call.completed" }.
curl
curl -X PUT https://api.clawcall.dev/me/webhook \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $CLAWCALL_API_KEY" \
  -d '{ "url": "https://example.com/clawcall/webhook", "headers": { "Authorization": "Bearer your-endpoint-token" } }'
# 200: { "webhook": { "url", "secret": "whsec_...", "enabled_at", "events", "headers": [{ "name": "Authorization", "value_preview": "Bearer ••••oken" }] } }

Errors

Errors share one envelope: a stable code, a readable message, and sometimes a reason, an action, and a quota. When an action.url is present, send people there exactly as returned. It is personalized.

402 Payment Required
{
  "error": {
    "code": "trial_exhausted",
    "message": "Free trial used (30 calls and 30 minutes). Choose a plan to keep calling.",
    "action": {
      "type": "choose_plan",
      "url": "https://clawcall.dev/dashboard/purchase?token=clawcall_sk_...",
      "sign_in_url": "https://clawcall.dev/sign-in?token=clawcall_sk_...",
      "description": "Pick Unlimited, Reserve, or Reserve Plus to continue"
    },
    "quota": {
      "used_seconds": 1800,
      "remaining_seconds": 0,
      "limit_seconds": 1800,
      "tier": "free"
    }
  }
}
CodeWhat to do
missing_fields400Send to, recipient, on_behalf_of, goal, and authority.allow.
invalid_phone400Use a US number in +1XXXXXXXXXX form. The area code cannot start with 0 or 1.
unsupported_destination400ClawCall only calls US numbers today.
invalid_call_request400Fix the field identified in the error. Required strings must be non-empty and nested objects are strict. For REST, a non-empty string task selects legacy behavior; extra keys, including structured fields, are ignored and their names are logged. A blank or whitespace-only task is omitted when structured fields are present. MCP rejects task alongside any structured-only field.
invalid_loop_in_user400Legacy/inbound flags must be boolean. Structured handoff validation uses invalid_call_request.
auth_required401This endpoint needs an account key. Follow action.url to sign up.
invalid_api_key401POST /call only: the key is unknown and this network address has no trial key. Replace the key, or send the request without one to start a trial.
trial_exhausted402The trial is used up. Send the user to action.url, or action.sign_in_url if they already have an account.
plan_required402The account has no active plan or trial left. Follow action.url.
phone_verification_required403A dashboard sign-in must verify a phone first. Follow action.url.
not_authorized403The call exists but belongs to another account or network address.
not_found404No call or route with that ID.
reserved_number_busy409Your reserved number is already on a call. Retry when it ends.
concurrent_call_limit409Your account already has a call in progress on a shared line. No new call was placed. Wait for it to finalize before placing the next call. Never retry in a loop.
reserved_number_unavailable409Your plan includes a reserved number but none is active. No shared caller ID is substituted.
call_request_rejected422The safety check declined this request. No call was placed. See message and action.description.
account_phone_unavailable422Loop-in needs one verified phone on the account.
dial_failed502The carrier refused the dial. Retry once if action.type is retry.
number_pool_exhausted503Every outbound line is busy. Wait about 15 seconds and retry once.
call_screening_unavailable503The safety check is briefly unavailable. No call was placed. Retry shortly.

Retry automatically only when the error says it is temporary: action.type is retry. Never retry a call that may already be running just because a request timed out. Poll it instead.

What are the limits and how much does it cost?

PlanWhat you get
Free trial$030 calls and 30 minutes, whichever lasts later. No card.
Unlimited$4.99/monthUnlimited calls from shared ClawCall numbers.
Unlimited Reserve$8.99/monthAdds one private reserved caller-ID number for outbound calls.
Unlimited Reserve Plus$14.99/monthAdds AI inbound answering on the active reserved number.

The free trial includes 30 calls and 30 minutes, whichever lasts later. A trial call counts only when it finalizes with at least 5 seconds of talk time, so failed dials and very short calls do not use one. Signed-in accounts track the trial per account and trial keys track it per network address. See pricing for the full comparison.

Check your balance

GET /balance returns tier, balance_seconds, balance_minutes, low_balance, and a plan or trial object. Legacy prepaid balances have neither and report numeric seconds. On an unlimited plan unlimited is true and both balance fields are null. Never read null as zero. A trial stays usable while trial.allowed is true, even at zero seconds, as long as calls remain.

curl
curl https://api.clawcall.dev/balance -H "X-Api-Key: $CLAWCALL_API_KEY"

Limits

  • Calls go to US +1 numbers only, and the agent speaks English only.
  • Trial and Unlimited accounts place one outbound call at a time on shared lines. A second call while one is running returns 409 concurrent_call_limit. Wait for the current call to finalize before placing the next. Anonymous callers share this limit per IP address.
  • If all shared lines are busy with other accounts, the call does not start. POST /call returns 503 number_pool_exhausted, and you retry shortly.
  • Unlimited Reserve and Reserve Plus calls always use your reserved number, one call at a time. A new call while that number is on a call returns 409 reserved_number_busy.
  • On shared-pool calls, looping you in uses a second shared line while you are connected.
  • ClawCall does not hold HIPAA, PCI, or SOC 2 attestation today.
  • Never ask a call recipient for the recipient's own card details, SSN or other government ID, passwords, one-time codes or health information. You may give the user's own details to a business or agency for the user's errand, including date of birth, address, account or member numbers, insurance IDs, health details and PINs. You may request the user's own records. This includes acting for a family member the user manages errands for.
  • For passwords, live one-time codes and full payment-card numbers, prefer loop-in or a private consult so the value is spoken live instead of written into the request, where it would be stored. Loop-in does not promise that recording or transcription stops.

Remote MCP

ChatGPT, Claude, and other hosts that support remote Streamable HTTP MCP with OAuth can use ClawCall directly. In ChatGPT, install the official ClawCall plugin, listed in ChatGPT's plugin directory: select Plugins, search “ClawCall”, select Install plugin, then Allow connection. ChatGPT needs no Developer mode, pasted URL, or OAuth configuration, and OpenAI dots use the same plugin. In other hosts, add the endpoint below. Then sign in to ClawCall and approve the calls:read and calls:write scopes. The host receives revocable OAuth tokens, not your API key.

Developers can still add the endpoint to ChatGPT by hand through Developer mode, but the plugin directory is the ChatGPT path for everyone else.

Remote MCP endpoint

https://api.clawcall.dev/mcp

Stateless Streamable HTTP on MCP revision 2026-07-28, with legacy negotiation for 2025-era clients. Step-by-step setup for each host:

View all hosts →

OAuth discovery architecture

The host reads the authorization-server metadata and the RFC 9728 protected-resource metadata, registers itself with Dynamic Client Registration, and then runs the OAuth 2.1 authorization-code flow with PKCE S256. It receives tokens only for the scopes the user approves.

StepEndpoint
Discoverhttps://api.clawcall.dev/.well-known/oauth-authorization-serverAuthorization-server metadata: authorize, token, registration, and revocation endpoints, and PKCE S256.
Discoverhttps://api.clawcall.dev/.well-known/oauth-protected-resource/mcpRFC 9728 protected-resource metadata for the MCP endpoint and its scopes.
Authorizehttps://api.clawcall.dev/authorizeAuthorization-code flow. The user signs in to ClawCall and approves scopes.
Tokenhttps://api.clawcall.dev/tokenExchanges the code (with the PKCE verifier) and refreshes tokens.
Registerhttps://api.clawcall.dev/registerDynamic Client Registration for a public OAuth client.
Revokehttps://api.clawcall.dev/revokeRevoke a token when the host disconnects.

Scopes

ScopeAllows
calls:readRead plan/trial state, call settings, reserved-number status, call status, captured transcript evidence when present, history, and temporary recording access when available.
calls:writePlace and hang up real phone calls or update call settings when you ask the assistant to do so.

The twelve tools

The host should call get_calling_guide with call_format: "structured" before any other ClawCall work. place_call_and_wait waits 45 seconds by default and up to 90. If the call is still running, keep polling get_call.

ToolWhat it does
get_calling_guideReadReturns current request fields, workflows, and example requests for building calls.
get_balanceReadRead the signed-in account's plan and remaining trial allowance.
get_call_settingsReadRead reusable voice/personality, reserved-number capabilities, and inbound-assistant availability.
update_call_settingsWriteUpdate global or inbound call settings in separate calls.
place_callWriteStart one real outbound phone call and return immediately.
place_call_and_waitWriteStart one call and wait up to 90 seconds for an initial result.
get_callReadRead a call's lifecycle, outcome, and the latest transcript lines when present.
get_call_transcriptReadRead the transcript evidence captured for a call.
list_callsReadRead recent call history for the connected account.
get_recordingReadGet a temporary, playable recording URL when one is available.
hangup_callWriteStop a queued or live phone call.
connect_webhookWriteSend call events to an agent's webhook trigger, such as a Grok Bot routine, then send a test event.

Verify, then disconnect when done

To test the connection without calling anyone, ask: "Check my ClawCall plan and remaining trial allowance. Do not place a call." The host should pick get_balance. Once connected, the host can choose ClawCall on its own for normal phone-call requests.

To disconnect, remove ClawCall in the host. Then open Dashboard → Settings → Connected AI assistants and choose Disconnect. Revoke happens there: ClawCall revokes that connection’s whole token family.

Agent skill

Skill-aware agents such as Claude Code, Cursor, and OpenClaw can install the ClawCall skill from ClawHub or GitHub. Before each operation the skill fetches the current guides from https://api.clawcall.dev/guides/v1/index.md, so it follows API changes without a reinstall.

It uses CLAWCALL_API_KEY when set. Otherwise the first call starts a trial and the skill saves the issued key. Then ask in plain words:

Prompt
Call Rosa's Bistro at +14155550123 and book a table for two this Friday at 7pm under Jordan Lee. Anything from 6:30 to 8pm is fine. Don't give a card number.

Troubleshooting

The call will not start
Read error.code. Most failures are invalid_phone (use +1 and 10 digits), missing_fields, or a used-up trial. For a JSON body, send Content-Type: application/json; without it the body is ignored and you get missing_fields.
The outcome is answered, but the task did not happen
answered is a network result. Check the transcript evidence actually captured to see what was said, then place a follow-up call with additional facts and questions if needed.
The transcript is null
Nobody has spoken yet, or the call never connected. Keep polling while lifecycle is not finalized.
The recording link expired
Recording links are temporary and expire on the carrier’s side, and recordings are deleted 10 minutes after the call ends. Fetch the recording soon after the call finalizes.
An API key stopped working
It may have been revoked in the dashboard. Create a new key and update whatever sends the X-Api-Key header.
The MCP host shows old or missing tools
Refresh the connector’s tool list in the host. ClawCall exposes twelve tools.