CallMyCall
Quickstart

Base URL: https://call-my-call-backend.fly.dev

curl -X POST https://call-my-call-backend.fly.dev/v1/start-call \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phone_number":"+46700000000","task":"Confirm my booking","language":"en","webhook":"https://example.com/cmc","metadata":{"client_request_id":"req_123"}}'
{
  "success": true,
  "sid": "CA1234567890abcdef"
}

Idempotency guidance: send your own metadata.client_request_id and dedupe retries in your system.

Result flow: keep returned sid, handle webhook events, then fetch call details by ID if needed.

Endpoints
POST /v1/start-call

Start an outbound AI call.

FieldTypeRequiredDescription
phone_numberstringyesE.164 destination number.
taskstringyesInstruction for AI behavior.
languagestringnoLanguage code such as sv, en, de.
tts_providerstringnoauto, openai, elevenlabs, azure.
openaiVoicestringnoOpenAI realtime voice id.
11labsVoicestringnoElevenLabs voice id (legacy alias).
elevenLabsVoicestringnoElevenLabs voice id.
elevenLabsModelstringnoElevenLabs model id.
azureVoicestringnoAzure Neural voice name.
stylestringnoAzure style hint.
rolestringnoAzure role hint.
azureBackgroundAudioobjectnoBackground audio config for Azure speech.
additionalPromptstringnoExtra instructions appended to task context.
recordbooleannoEnable call recording.
max_durationnumbernoMaximum connected call duration (seconds).
max_queue_timenumbernoMaximum queue wait duration (seconds).
personaobjectnoCaller persona fields (name, phone, etc).
transfer_numberstringnoE.164 transfer target for handoff flows.
from_numberstringnoOverride caller id (must be verified).
share_policyobjectnoControls how sensitive fields may be shared.
enable_calendarbooleannoEnable calendar tools in-call.
calendar_permissionsobjectnoCalendar read/write permissions.
webhookstringnoHTTPS endpoint for events.
webhook_eventsstring[]noEvent filters. Default is call_completed.
metadataobjectnoArbitrary client metadata (for correlation/idempotency).
userOnCallbooleannoUser joins call before AI takeover.
userPhonestringconditionalRequired when userOnCall is true.
monitoredbooleannoDeprecated alias of userOnCall.
user_phonestringnoDeprecated alias of userPhone.
{
  "success": true,
  "sid": "CA1234567890abcdef"
}
{
  "error": "Insufficient credits",
  "credits_balance": 3,
  "minimum_required": 5
}
curl -X POST https://call-my-call-backend.fly.dev/v1/start-call \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phone_number":"+46700000000","task":"Confirm booking","language":"en","webhook":"https://example.com/cmc"}'
const res = await fetch("https://call-my-call-backend.fly.dev/v1/start-call", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    phone_number: "+46700000000",
    task: "Confirm booking",
    language: "en",
    webhook: "https://example.com/cmc",
  }),
});
const data = await res.json();
POST /v1/end-call

End an active call.

FieldTypeRequiredDescription
callSidstringyesTwilio call SID from start response.
{
  "ok": true,
  "alreadyClosed": false,
  "transcript": []
}
{
  "error": "Missing callSid"
}
curl -X POST https://call-my-call-backend.fly.dev/v1/end-call \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"callSid":"CA1234567890abcdef"}'
const res = await fetch("https://call-my-call-backend.fly.dev/v1/end-call", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ callSid }),
});
const data = await res.json();
POST /v1/calls/:callSid/activate

Activate AI takeover for user-on-call sessions.

FieldTypeRequiredDescription
taskstringnoOverride current AI task for takeover.
drop_userbooleannoDisconnect user leg when AI takes over.
additional_promptstringnoAdditional instructions appended at activation.
transfer_numberstringnoTransfer target if handoff is needed.
{
  "success": true,
  "ai_activated": true,
  "user_dropped": true,
  "message": "Call redirected to AI bidirectional stream"
}
{
  "error": "Call not found or not a userOnCall session"
}
curl -X POST https://call-my-call-backend.fly.dev/v1/calls/CA1234567890abcdef/activate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"task":"Take over now","drop_user":true}'
const res = await fetch(`${baseUrl}/v1/calls/${callSid}/activate`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ task: "Take over now", drop_user: true }),
});
const data = await res.json();
GET /v1/calls/:callId

Fetch call details and recent transcripts by call id/sid.

ParamTypeRequiredDescription
callIdstringyesCall SID from start response.
{
  "call": {
    "call_id": "CA1234567890abcdef",
    "status": "completed",
    "to": "+46700000000",
    "created_at": "2026-02-14T10:00:00.000Z"
  },
  "transcripts": [
    { "role": "assistant", "text": "Hello.", "seq": 1 }
  ]
}
curl -X GET https://call-my-call-backend.fly.dev/v1/calls/CA1234567890abcdef \
  -H "Authorization: Bearer YOUR_API_KEY"
const res = await fetch(`${baseUrl}/v1/calls/${callSid}`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json();
GET /v1/calls/:callId/transcripts/stream

Subscribe to realtime transcript updates using Server-Sent Events.

ParamTypeRequiredDescription
callIdstringyesCall SID from start response.
event: added
data: {"role":"assistant","text":"Hello.","seq":1}

event: ping
data: {"ts":1739511142}
curl -N https://call-my-call-backend.fly.dev/v1/calls/CA1234567890abcdef/transcripts/stream
const es = new EventSource(`${baseUrl}/v1/calls/${callSid}/transcripts/stream`);
es.addEventListener("added", (event) => {
  const turn = JSON.parse(event.data);
  console.log(turn);
});

Use this when you need live transcript rendering before call completion.

GET /v1/calls/:callSid/recording

Get a signed recording URL for a completed call (if recording enabled).

QueryTypeRequiredDescription
formatstringnomp3 or wav (default mp3).
{
  "recording_url": "https://call-my-call-backend.fly.dev/v1/recordings/RE123.mp3?token=...",
  "expires_at": "2026-02-14T12:00:00.000Z"
}
{
  "error": "Recording not found for this call"
}
curl -X GET "https://call-my-call-backend.fly.dev/v1/calls/CA1234567890abcdef/recording?format=mp3" \
  -H "Authorization: Bearer YOUR_API_KEY"
const res = await fetch(`${baseUrl}/v1/calls/${callSid}/recording?format=mp3`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json(); // { recording_url, expires_at }
POST /v1/calls/:callSid/transfer

Transfer an active call to another destination.

FieldTypeRequiredDescription
destinationstringyesE.164 transfer destination.
warmbooleannoDefaults to true for warm transfer.
reasonstringnoOptional transfer reason for logs/traceability.
{
  "success": true,
  "transferType": "warm",
  "callSid": "CA1234567890abcdef"
}
curl -X POST https://call-my-call-backend.fly.dev/v1/calls/CA1234567890abcdef/transfer \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"destination":"+46123456789","warm":true,"reason":"human_requested"}'
const res = await fetch(`${baseUrl}/v1/calls/${callSid}/transfer`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    destination: "+46123456789",
    warm: true,
    reason: "human_requested",
  }),
});
const data = await res.json();
GET /v1/recordings/:sid.:ext

Stream recording audio bytes using a signed token URL from the recording endpoint.

ParamTypeRequiredDescription
sidstringyesRecording SID.
extstringyesmp3 or wav.
tokenstringyesSigned short-lived token query param.
curl -L "https://call-my-call-backend.fly.dev/v1/recordings/RE123.mp3?token=SIGNED_TOKEN"
const audioRes = await fetch(recordingUrlFromPreviousStep);
const audioBuffer = await audioRes.arrayBuffer();
PATCH /v1/calls/:callSid

Update active call parameters while call is running.

FieldTypeRequiredDescription
taskstringnoReplace active task.
transfer_numberstringnoSet E.164 transfer target.
additional_promptstringnoAppend to prompt context.
max_durationnumbernoConnected call max duration seconds.
max_queue_timenumbernoQueue max duration seconds.
curl -X PATCH https://call-my-call-backend.fly.dev/v1/calls/CA1234567890abcdef \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"task":"Updated task","transfer_number":"+46123456789"}'
const res = await fetch(`${baseUrl}/v1/calls/${callSid}`, {
  method: "PATCH",
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
  body: JSON.stringify({ task: "Updated task", transfer_number: "+46123456789" }),
});
const data = await res.json();
GET /v1/calls/:callSid/conference-status

Get user-on-call conference state and participants.

curl -X GET https://call-my-call-backend.fly.dev/v1/calls/CA1234567890abcdef/conference-status \
  -H "Authorization: Bearer YOUR_API_KEY"
const res = await fetch(`${baseUrl}/v1/calls/${callSid}/conference-status`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json();
POST /v1/calls/:callSid/drop-user

Disconnect the user leg from a user-on-call session.

curl -X POST https://call-my-call-backend.fly.dev/v1/calls/CA1234567890abcdef/drop-user \
  -H "Authorization: Bearer YOUR_API_KEY"
const res = await fetch(`${baseUrl}/v1/calls/${callSid}/drop-user`, {
  method: "POST",
  headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json();
POST /v1/verify-caller-id

Start caller id verification for custom from_number usage.

FieldTypeRequiredDescription
phone_numberstringyesE.164 number to verify.
curl -X POST https://call-my-call-backend.fly.dev/v1/verify-caller-id \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phone_number":"+46123456789"}'
const res = await fetch(`${baseUrl}/v1/verify-caller-id`, {
  method: "POST",
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
  body: JSON.stringify({ phone_number: "+46123456789" }),
});
const data = await res.json();
GET /v1/verification-status/:id

Check verification state (pending, verified, failed).

curl -X GET https://call-my-call-backend.fly.dev/v1/verification-status/CA_VERIFICATION_ID \
  -H "Authorization: Bearer YOUR_API_KEY"
const res = await fetch(`${baseUrl}/v1/verification-status/${verificationId}`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json();
GET /v1/verified-caller-ids

List verified caller ids available to your key/account.

curl -X GET https://call-my-call-backend.fly.dev/v1/verified-caller-ids \
  -H "Authorization: Bearer YOUR_API_KEY"
const res = await fetch(`${baseUrl}/v1/verified-caller-ids`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json();
DELETE /v1/verified-caller-ids/:phone_number

Remove a verified caller id owned by your API key.

curl -X DELETE https://call-my-call-backend.fly.dev/v1/verified-caller-ids/+46123456789 \
  -H "Authorization: Bearer YOUR_API_KEY"
const res = await fetch(`${baseUrl}/v1/verified-caller-ids/${encodeURIComponent(phoneNumber)}`, {
  method: "DELETE",
  headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json();
POST /v1/webhook/test

Send a synthetic webhook payload to your endpoint.

FieldTypeRequiredDescription
webhook_urlstringyesHTTPS URL to receive test event.
eventstringnoDefaults to call_completed.
curl -X POST https://call-my-call-backend.fly.dev/v1/webhook/test \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url":"https://example.com/cmc","event":"call_completed"}'
const res = await fetch(`${baseUrl}/v1/webhook/test`, {
  method: "POST",
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
  body: JSON.stringify({ webhook_url: "https://example.com/cmc", event: "call_completed" }),
});
const data = await res.json();
POST /v1/elevenlabs/voices

Proxy endpoint for ElevenLabs voice cloning via multipart upload.

FieldTypeRequiredDescription
namestringyesVoice name label.
filesfile[]yesOne or more audio samples.
remove_background_noisebooleannoNoise removal hint for cloning.
curl -X POST https://call-my-call-backend.fly.dev/v1/elevenlabs/voices \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "name=My Voice" \
  -F "files=@sample1.wav" \
  -F "files=@sample2.wav"
const form = new FormData();
form.append("name", "My Voice");
form.append("files", file1);
form.append("files", file2);

const res = await fetch(`${baseUrl}/v1/elevenlabs/voices`, {
  method: "POST",
  headers: { Authorization: `Bearer ${apiKey}` },
  body: form,
});
const data = await res.json();
GET /v1/recordings/:callId

Legacy recording stream route (mp3 stream by call id).

curl -L https://call-my-call-backend.fly.dev/v1/recordings/CA1234567890abcdef \
  -H "Authorization: Bearer YOUR_API_KEY"
const res = await fetch(`${baseUrl}/v1/recordings/${callSid}`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const audio = await res.arrayBuffer();
User-On-Call flow and states
StateBehaviorNext
initiatedTarget + user legs are dialed.connected or failed
connectedUser and target talk. AI can stay silent/listening.ai_takeover or completed
ai_takeover/activate redirects call to AI realtime stream.active
activeAI speaks and executes task.transferred or completed
completedCall ends and completion webhook fires.-
Webhooks

Set webhook and optional webhook_events in start-call payload.

EventWhenNotes
call_startedRealtime stream startsBest-effort signal
transcript_updatedNew transcript segmentsCan fire multiple times
price_updatedPost-call price enrichmentMay arrive after completion
call_completedCall closesDefault if events list omitted
{
  "event": "call_completed",
  "call_id": "CA1234567890abcdef",
  "sid": "CA1234567890abcdef",
  "timestamp": 1739511142,
  "data": {
    "status": "completed",
    "to": "+46700000000",
    "summary": { "goal_completed": true },
    "transcripts": [
      { "role": "assistant", "content": "Hello.", "timestamp": "2026-02-14T10:00:00.000Z", "seq": 1 }
    ]
  }
}
Delivery contractCurrent behavior
Success conditionAny HTTP 2xx response.
RetriesUp to 3 attempts total.
Retry policyRetry on network errors and 5xx. No retry on 4xx.
Backoff600ms then 1200ms between retries.
Request timeout10 seconds per attempt.
SignatureX-Callmycall-Signature: t=TIMESTAMP,v1=HMAC_SHA256 (when signing secret is configured).

Deduplicate by event + sid + timestamp.

Errors
HTTPExampleCause
400Missing phone_numberValidation failed.
401Authorization header with API key is requiredMissing/invalid auth.
402Subscription inactive / Insufficient creditsBilling pre-check failed.
404Call not foundUnknown SID or already closed.
429Too Many RequestsApply client backoff and retry.
500Failed to ...Provider/internal error.
Billing & credits

Mental model: provider usage cost is converted to credits with margin, then rounded up per call.

RuleBehavior
What countsConnected call time, Twilio media stream, transcription, realtime tokens, and TTS.
RoundingTotal call charge is rounded up to the nearest whole credit.
Minimum chargeNo separate fixed minimum fee. Rounded call total can still become 1 credit.
Queue waitQueue/hold time counts once the call is connected.
Credit guardStart-call may reject when balance is below required minimum.
ScenarioTypical credits
2-minute call with transcription + TTSAbout 4 credits
8-minute queue + 2-minute conversationAbout 10 credits
45-second short confirmation callAbout 2 credits