EpiCall AI

EpiCall AI API

Start phone campaigns, add contacts, get call results and transcripts, and put chat and voice agents in your own website, app or server. Every request is HTTPS with JSON bodies.

Quick start

  1. Sign in to the console and open Administration → Deploy Keys. No account yet? Start a free trial.
  2. Create a server key. It is shown once, so store it safely.
  3. Send it as a Bearer token. This lists your agents:
Base URL
https://epicall.ai/console/api/v1
bash
export API_KEY="ek_sk_…"   # your server key

curl https://epicall.ai/console/api/v1/agents \
  -H "Authorization: Bearer $API_KEY"

Authentication

Send your key in the Authorization header on every request:

Header
Authorization: Bearer <your key>
KeyStarts withUse it fromNotes
Server keyek_sk_Your backend onlyFull access to your workspace: campaigns, call logs, chat. Never put it in a web page or mobile app.
Website keyek_pk_Web pagesWorks only from the website domains you list on the key. Used by the voice and chat widgets.
  • Keys are created and revoked in Deploy Keys. Revoking takes effect immediately.
  • A key works only on the console domain it was created on (epicall.ai for EpiCall AI).
  • Endpoints marked Server key refuse website keys with 403 secret_key_required.

Errors

Errors return an HTTP status and a JSON body with a stable code:

json
{ "error": { "code": "invalid_key", "message": "That key is not valid, or has been revoked." } }
StatusCodeMeaning
401missing_keyNo Authorization: Bearer header
401invalid_keyUnknown or revoked key, or a key from another domain
403secret_key_requiredA website key was sent to a server-key endpoint
403origin_not_allowedA website key was used from a domain not listed on it
402subscription_inactiveThe trial ended or the subscription is not active
402quota_exceededThe plan's voice minutes or conversations are used up
429rate_limitedToo many requests; try again in a minute

Each endpoint below lists its own additional errors.

Rate limits

Limits are per key, over a rolling 60 seconds.

EndpointsRequests per minute
Agents, call logs, campaigns, contacts, voice sessions60
Chat messages, history, config, handover1,200 (40 per visitor IP with a website key)
Chat updates (polling)6,000 (30 per visitor IP with a website key)

Agents

GET/agentsServer key

Lists your voice agents. Use an agent's id as agent_id elsewhere.

bash
curl https://epicall.ai/console/api/v1/agents \
  -H "Authorization: Bearer $API_KEY"
200 OK
{ "data": [ { "id": "support_agent", "name": "Support Agent", "languages": ["en", "hi"] } ] }

Call logs

GET/callsServer key

Your calls from every channel (phone campaigns, website widget, WhatsApp, app), newest first.

QueryTypeRequiredNotes
agent_idstringNoOnly this agent's calls
fromdate or date-timeNoFirst day, e.g. 2026-09-01 (India time), or an exact ISO date-time
todate or date-timeNoLast day, included, e.g. 2026-09-30, or an exact ISO date-time
tzIANA time zoneNoZone for from/to days, e.g. Europe/London. Default Asia/Kolkata
daysintegerNoOnly the last N days (instead of from/to)
limitintegerNo1–200, default 50
offsetintegerNoCalls to skip, for paging: offset=200 with limit=200 is the second page. Default 0
bash
curl "https://epicall.ai/console/api/v1/calls?from=2026-09-01&to=2026-09-30&agent_id=support_agent&limit=200&offset=0" \
  -H "Authorization: Bearer $API_KEY"
200 OK
{ "total": 1234, "limit": 200, "offset": 0, "data": [ {
  "id": "sess_abc123", "agent_id": "support_agent", "agent_name": "Support Agent",
  "started_at": "2026-09-29T10:15:02.000Z", "duration_seconds": 184,
  "cost_usd": 0.42, "tool_calls": 3, "avg_response_seconds": 0.9, "has_details": true
} ] }

total is how many calls match in all. Keep adding limit to offset until you have them all.

Errors: 400 invalid_range when from or to can't be read, or from is after to.

GET/calls/{id}Server key

One call with its transcript. caller_ref is the reference you (or a campaign) attached to the call.

bash
curl https://epicall.ai/console/api/v1/calls/sess_abc123 \
  -H "Authorization: Bearer $API_KEY"
200 OK
{
  "id": "sess_abc123", "agent_id": "support_agent",
  "started_at": "2026-09-29T10:15:02.000Z", "ended_at": "2026-09-29T10:18:06.000Z",
  "duration_seconds": 184, "cost_usd": 0.42, "caller_ref": "order-1001",
  "transcript": [
    { "type": "speech", "speaker": "agent", "text": "Hi, this is Priya from Acme…", "at": "…" },
    { "type": "speech", "speaker": "caller", "text": "Yes, go ahead.", "at": "…" },
    { "type": "tool_call", "tool": "hubspot_lookup_contact", "duration_ms": 412, "at": "…" },
    { "type": "language_switch", "language": "hi", "at": "…" }
  ]
}

Transcript entry types: speech, tool_call, language_switch, reengage, call_ended_idle. Errors: 404 call_not_found.

Campaigns

A campaign is an agent calling a list of people. You create it as a draft, add contacts, then launch it. It calls within the days and hours you set, retries people who don't answer, and analyses every call for an outcome and whether the goal was met.

Create→Add contacts→Launch→Get results or webhooks
POST/campaignsServer key

Creates a campaign in draft, optionally with up to 5,000 contacts. No one is called until you launch it.

FieldTypeRequiredNotes
namestringYesUp to 120 characters
agent_idstringYesThe agent that makes the calls
channel"phone" | "whatsapp_message"NoDefault phone. whatsapp_message sends an approved WhatsApp template instead of calling
goalstringNoWhat counts as success, e.g. “Book a demo”. Used to judge goal_achieved. Up to 500 characters
agent_b_idstringNoA second agent for an A/B test; contacts alternate between the two
timezoneIANA nameNoDefault Asia/Kolkata
daysinteger[]NoDays to call, 0 = Sunday. Default [1,2,3,4,5,6] (Mon–Sat)
window_start / window_end"HH:MM"NoDaily calling hours in the campaign's timezone. Default 10:00–19:00
start_at / end_atISO date-timeNoOptional start and finish. Reaching end_at completes the campaign
max_concurrentintegerNoCalls at the same time, 1–20. Default 2
calls_per_minuteintegerNoNew calls per minute, 1–60. Default 4
max_attemptsintegerNoTries per person, 1–5. Default 3
retry_after_minintegerNoMinutes before retrying a no-answer, 10–10080. Default 60
personalizebooleanNoThe agent greets each person by name and uses their fields. Default false
webhook_urlhttps URLNoReceives a POST for every finished contact and when the campaign completes. See Webhooks
followup_onoutcome[]NoSend a WhatsApp template after these outcomes (needs wa_phone_number_id and wa_template)
wa_phone_number_id / wa_template / wa_languagestringNoYour connected WhatsApp number and approved template, for whatsapp_message campaigns and follow-ups
contactsContact[]NoUp to 5,000 here; add more later. See Campaign contacts
bash
curl -X POST https://epicall.ai/console/api/v1/campaigns \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "October renewals",
    "agent_id": "sales_agent",
    "goal": "Book a renewal call",
    "days": [1,2,3,4,5], "window_start": "10:00", "window_end": "18:30",
    "max_concurrent": 3, "calls_per_minute": 6,
    "max_attempts": 3, "retry_after_min": 120,
    "personalize": true,
    "webhook_url": "https://example.com/hooks/calls",
    "contacts": [
      { "phone": "+919876543210", "name": "Priya Sharma", "fields": { "plan": "Pro", "renewal_date": "2026-10-15" } }
    ]
  }'
201 Created
{ "id": 12, "status": "draft",
  "imported": { "added": 1, "duplicates": 0, "invalid": 0, "do_not_call": 0, "invalid_samples": [] } }

Errors: 400 invalid_body, 422 invalid_settings (the message says which setting), 422 invalid_contacts.

PATCH/campaigns/{id}Server key

Starts, pauses, resumes or stops a campaign. Body: { "action": "launch" | "pause" | "resume" | "stop" }

ActionAllowed whenResult
launchdraft, with at least one contactrunning, or scheduled if start_at is in the future. More than 500 contacts: pending_approval until an owner approves it in the console
pauserunning or scheduledpaused. Calls already in progress finish normally
resumepausedrunning (still only within the calling hours)
stoprunning, scheduled, paused or pending_approvalstopped. Everyone not yet called is skipped. Final
bash
curl -X PATCH https://epicall.ai/console/api/v1/campaigns/12 \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action":"launch"}'
200 OK
{ "id": 12, "status": "running" }

Errors: 404 not_found, 422 invalid_action, 409 not_allowed (e.g. “Only a draft can be launched.”).

Launching places real phone calls, which use your plan's voice minutes.
GET/campaignsServer key

Your latest 200 campaigns, newest first, each with its settings and live stats.

bash
curl https://epicall.ai/console/api/v1/campaigns \
  -H "Authorization: Bearer $API_KEY"
GET/campaigns/{id}Server key

One campaign with its settings and live stats.

200 OK
{ "id": 12, "name": "October renewals", "channel": "phone", "status": "running",
  "agent_id": "sales_agent", "agent_b_id": null, "goal": "Book a renewal call",
  "schedule": { "timezone": "Asia/Kolkata", "start_at": null, "end_at": null,
                "days": [1,2,3,4,5], "window_start": "10:00", "window_end": "18:30" },
  "pacing": { "max_concurrent": 3, "calls_per_minute": 6 },
  "retries": { "max_attempts": 3, "retry_after_min": 120 },
  "personalize": true, "webhook_url": "https://example.com/hooks/calls",
  "created_at": "…", "launched_at": "…", "finished_at": null,
  "stats": { "contacts": 800, "dialled": 310, "connected": 190, "goals_achieved": 42,
             "attempts": 402, "in_call": 2, "avg_duration_s": 95,
             "by_status": { "pending": 480, "done": 300, "retry": 20 },
             "by_outcome": { "interested": 42, "no_answer": 80 } } }

Campaign contacts

POST/campaigns/{id}/contactsServer key

Adds up to 5,000 contacts per request, up to 20,000 per campaign. Works on a draft, scheduled, running or paused campaign; a running one calls new people as their turn comes.

FieldTypeRequiredNotes
contacts[].phonestringYesIndian mobile number: +919876543210, 919876543210 or 9876543210
contacts[].namestringNoUp to 120 characters
contacts[].fieldsobjectNoUp to 30 key/value pairs (e.g. plan, due_date) the agent can use, and WhatsApp template variables
bash
curl -X POST https://epicall.ai/console/api/v1/campaigns/12/contacts \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contacts":[{"phone":"98765 43210","name":"Rahul","fields":{"city":"Pune"}}]}'
201 Created
{ "added": 1, "duplicates": 0, "invalid": 0, "do_not_call": 0, "invalid_samples": [] }
  • Duplicates (in the request or already in the campaign) are counted and skipped.
  • Numbers on your do-not-call list are skipped. Anyone who asks not to be called is added to it automatically.
  • Errors: 404 not_found, 409 finished, 422 invalid_contacts, 422 too_many.
GET/campaigns/{id}/contactsServer key

Per-person results, most recently updated first.

QueryTypeRequiredNotes
statusstringNopending, calling, retry, callback, done, failed or skipped
outcomestringNoSee Statuses and outcomes
limitintegerNoUp to 1,000. Default 100
offsetintegerNoFor paging. Default 0
bash
curl "https://epicall.ai/console/api/v1/campaigns/12/contacts?status=done&limit=500" \
  -H "Authorization: Bearer $API_KEY"
200 OK
{ "total": 300, "contacts": [ {
  "id": 345, "phone": "+919876543210", "name": "Priya Sharma", "fields": { "plan": "Pro" },
  "status": "done", "attempts": 1, "outcome": "interested", "outcome_label": "Interested",
  "goal_achieved": true, "summary": "Agreed to renew; wants the invoice by email.",
  "details": { "email": "priya@example.com", "sentiment": "positive" },
  "duration_s": 132, "next_attempt_at": null, "updated_at": "2026-09-29T10:20:00.000Z"
} ] }

Calling one person

To have an agent call a single person from your system (a new lead, a missed payment, an appointment reminder), create a campaign with that one contact and launch it. Set the calling hours to suit you; the call goes out within a few seconds when inside them.

bash
# 1. Create the campaign with one contact
curl -X POST https://epicall.ai/console/api/v1/campaigns \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Lead 7781 follow-up",
    "agent_id": "sales_agent",
    "goal": "Book a demo",
    "days": [0,1,2,3,4,5,6], "window_start": "09:00", "window_end": "21:00",
    "max_attempts": 2, "retry_after_min": 30,
    "personalize": true,
    "webhook_url": "https://example.com/hooks/calls",
    "contacts": [ { "phone": "+919876543210", "name": "Amit", "fields": { "source": "website form" } } ]
  }'

# 2. Launch it (use the id from step 1)
curl -X PATCH https://epicall.ai/console/api/v1/campaigns/12 \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action":"launch"}'

The result arrives at your webhook_url as contact.completed, or poll GET /campaigns/12/contacts.

Webhooks

Set webhook_url on a campaign to receive a POST when each person's calls are finished, and when the whole campaign completes.

EventSent when
contact.completedA contact reaches done or failed (after all retries)
campaign.completedEveryone has been called, or end_at is reached. Not sent when you stop a campaign
POST to your webhook_url
X-EpiCall-Event: contact.completed
X-EpiCall-Signature: sha256=5d41402abc4b2a76b9719d911017c592…
Content-Type: application/json

{ "event": "contact.completed", "sent_at": "2026-09-29T10:20:01.000Z",
  "campaign": { "id": 12, "name": "October renewals", "status": "running" },
  "contact": { "phone": "+919876543210", "name": "Priya Sharma", "fields": { "plan": "Pro" },
    "outcome": "interested", "outcome_label": "Interested", "goal_achieved": true,
    "summary": "Agreed to renew; wants the invoice by email.",
    "details": { "sentiment": "positive", "callback_at": null },
    "duration_s": 132, "attempts": 1 } }

Verify the signature

The signing secret (whsec_…) is on the campaign's page in the console. Compute an HMAC-SHA256 of the raw request body with it and compare:

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

function isGenuineWebhook(rawBody, signatureHeader, secret) {
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected), b = Buffer.from(signatureHeader || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Python
import hmac, hashlib

def is_genuine_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header or "")

Respond with any 2xx within 5 seconds. Deliveries are not retried, so poll GET /campaigns/{id}/contacts if you need to catch up.

Voice sessions

Talk to an agent in real time from a browser, an app, or your own phone system.

POST/call-tokensServer or website key

Returns a single-use WebSocket URL, valid for 60 seconds, for one voice session with an agent.

FieldTypeRequiredNotes
agent_idstringYesOne of your voice agents
bash
curl -X POST https://epicall.ai/console/api/v1/call-tokens \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent_id":"support_agent"}'
201 Created
{ "agent_id": "support_agent",
  "websocket_url": "wss://…/voice-proxy/ws/session?agent_config=support_agent&ticket=…",
  "expires_at": "2026-09-30T12:01:00.000Z" }
  1. Connect to websocket_url and first send { "client_name": "…", "caller_ref": "your-ref" }.
  2. Stream the caller's audio as binary 16 kHz, 16-bit, mono PCM.
  3. The agent's audio comes back as binary 24 kHz, 16-bit, mono PCM, with JSON events such as session_started, user_transcript and assistant_text.
  4. Close with code 1000. Your caller_ref appears on the call in GET /calls/{id}.

Errors: 400 missing_agent_id, 404 agent_not_found, 402 quota_exceeded, 503 calls_unavailable.

Chat API

Use your chat agents from your own app or server, including handing a conversation to your team.

POST/chat/messagesServer or website key

Sends one visitor message and returns the agent's reply.

FieldTypeRequiredNotes
agent_idstringYesOne of your chat agents
session_idstringYes16–128 letters, digits, - or _. Generate one (a UUID) per conversation and reuse it
textstringYesUp to 4,000 characters
page_urlstringNoThe page the visitor is on
visitorobjectNo{ id, name, email, phone, attributes } — attributes: up to 20 keys
bash
curl -X POST https://epicall.ai/console/api/v1/chat/messages \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent_id":"support_chat","session_id":"3f6c2b1e-8a9d-4c1b-9f0e-1234567890ab",
       "text":"Where is my order A1001?",
       "visitor":{"id":"cust-9","name":"Priya","email":"priya@example.com"}}'
200 OK
{ "replies": ["Let me check that for you…"], "handover": { "status": "bot", "assignee": null } }

While your team has the conversation, replies is empty; their messages arrive through /chat/updates. Errors: 400 invalid_session_id, 400 missing_text, 413 message_too_long, 404 agent_not_found, 402 quota_exceeded.

GET/chat/historyServer or website key

The whole visible conversation. Query: agent_id, session_id.

200 OK
{ "messages": [ { "role": "user", "text": "Hi", "at": "…" },
                { "role": "assistant", "text": "Hello!", "at": "…" },
                { "role": "team", "text": "Priya here", "at": "…", "author": "Priya" } ],
  "handover": { "status": "human", "assignee": "Priya", "team_online": true }, "last_id": 12 }
GET/chat/updatesServer or website key

New team messages since after (a message id), for polling. Query: agent_id, session_id, after.

POST/chat/handoverServer or website key

Hands the conversation to your team's inbox: { agent_id, session_id, visitor? }. If nobody is online, send { agent_id, session_id, email } so the team can reply by email.

GET/chat/configServer or website key

The chat widget's appearance and settings for agent_id, as set in the console.

Website widgets

Paste one tag before </body>. Use a website key that lists your site's domain. Each agent's Integrations tab in the console has these with your agent and key filled in.

Voice agent button
<script src="https://epicall.ai/console/widget.js"
  data-agent-id="support_agent"
  data-key="ek_pk_…"
  data-label="Talk to us"
  data-position="bottom-right"></script>
Chat widget
<script src="https://epicall.ai/console/chat-widget.js"
  data-agent-id="support_chat"
  data-key="ek_pk_…"></script>

The chat widget's colours, greeting, pre-chat form, WhatsApp button and “call us” button are set in the console. Pass signed-in visitor details with data-user-* attributes or ChatWidget.identify().

App integrations

Connect these apps in Agent management → Integrations. Your agents can then use them during calls and chats. No code is needed.

CategoryAppsWhat agents can do
Calendar and bookingGoogle Calendar, Outlook / Microsoft 365, Calendly, Cal.com, Square Appointments, Acuity, Housecall ProCheck free slots, book, reschedule and cancel
CRMHubSpot, Salesforce, Zoho CRM, Pipedrive, Microsoft Dynamics 365, GoHighLevelRecognise the caller, save the contact, log the call, create follow-up tasks
HelpdeskZendesk, Freshdesk, Intercom, Jira Service Management, Salesforce Service Cloud, ServiceNowFind open tickets, create a ticket with the call summary, add notes, escalate
KnowledgeZendesk Guide, Confluence, NotionAnswer from your help articles
EmailGmail, SendGrid, Postmark, Zoho Mail, Amazon SESEmail a confirmation or follow-up
PaymentsStripe, Square, PayPalLook up payments, send a payment link, confirm payment
Orders and ERPShopify, WooCommerce, Oracle NetSuite, Odoo, Dynamics 365 Business CentralOrder status and tracking, stock and prices, cancellations, returns
Customer engagementMoEngage, WebEngage, CleverTapUpdate the customer profile and record call events
ChannelsWhatsApp Business, phone calls, website voice and chatAnswer WhatsApp messages and calls, run phone and WhatsApp campaigns

For anything else, add a custom HTTP tool on the console's Tools screen.

Statuses and outcomes

Values
Campaign statusdraft · pending_approval · scheduled · running · paused · completed · stopped
Contact statuspending · calling · retry · callback · done · failed · skipped
Call outcomeinterested · not_interested · callback · wrong_number · do_not_call · voicemail · no_answer · failed · other · sent
  • Phone campaigns call Indian mobile numbers (+91, starting 6–9). WhatsApp campaigns accept international numbers.
  • When someone asks to be called later, the agent books a callback at the time they gave.
  • Outcome do_not_call adds the number to your do-not-call list for every campaign.

Questions about the EpiCall AI API? Create a key in Deploy Keys and try the examples above; every one runs as written. No account yet? Start a free trial or talk to us.

Ready to put an AI voice agent on every call?

Talk to us about your call volumes, languages, and channels — we'll show you EpiCall AI handling a real conversation in under a week.

Talk to Sales