Base URL
https://server.heyrik.com/api/v1 — every path in the API reference is relative to this.
Everything your server needs: the base URL and auth, the results webhook for every finished call, the live-leads webhook that feeds a campaign from your own system, and how lead fields reach the agent.
Your server talks to HeyRik in two directions. It calls the API to create agents and campaigns and to push leads, and HeyRik calls your server with the result of every finished call. This page is the contract for both — copy the examples and you are integrated.
The results webhook is a URL you own that HeyRik POSTs to after each call. The live-leads webhook is a URL HeyRik owns that your system POSTs leads to. They are independent — use either or both.
The same three rules apply to every request.
https://server.heyrik.com/api/v1 — every path in the API reference is relative to this.
Authorization: Bearer <key> or X-API-Key: <key>. Keys are created in the dashboard under API keys and only ever see their own account.
Success is { "data": … }. Errors are { "error": "<machine_code>", "hint"?: "…" } with a matching HTTP status. Lists may add total, page, limit.
API-key traffic is throttled per key (429 rate_limited — back off and retry). The live-leads webhook allows 600 posts per minute per source IP.
const api = async (path, init = {}) => {
const r = await fetch("https://server.heyrik.com/api/v1" + path, {
...init,
headers: {
Authorization: `Bearer ${process.env.HEYRIK_API_KEY}`,
"Content-Type": "application/json",
...init.headers,
},
});
const body = await r.json();
if (!r.ok) throw new Error(`${r.status} ${body.error}: ${body.hint ?? ""}`);
return body.data;
};Set webhook_url on a campaign. HeyRik POSTs JSON to it when a lead's call finishes and once more when the whole campaign completes.
curl -X PATCH https://server.heyrik.com/api/v1/campaigns/cmp_4a19 \
-H "Authorization: Bearer $HEYRIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "webhook_url": "https://api.acme.com/heyrik/results" }'{
"event": "contact_completed",
"campaign_id": "cmp_4a19",
"campaign_name": "July outreach",
"contact_id": "cct_9b1e",
"phone": "+919876543210",
"status": "completed",
"outcome": "completed",
"attempts": 1,
"category": "hot",
"variables": { "name": "Ravi", "order_id": "AC-8842" },
"call_id": "call_8eab93e5",
"call": {
"summary": "Ravi confirmed the order and asked for a callback on Monday.",
"outcome": "completed",
"duration_ms": 214000,
"recording_url": "https://example.com/rec.mp3",
"extracted_variables": { "callback_requested": "yes", "interest_level": "high" }
},
"ts": "2026-08-24T13:41:07.512Z"
}{
"event": "campaign_completed",
"campaign_id": "cmp_4a19",
"campaign_name": "July outreach",
"counts": { "pending": 0, "calling": 0, "completed": 231, "failed": 19, "retry": 0, "total": 250 },
"ts": "2026-08-24T18:02:44.001Z"
}HeyRik waits up to 12 seconds and does not retry a failed delivery. Acknowledge first, then do your work in a queue.
Key your storage on contact_id + call_id. A lead with retries produces one event per attempt that ended; status is the final one for that attempt.
call.extracted_variables carries the agent's output variables and any campaign extraction_schema fields; category is the classify bucket, if you set one.
The webhook has no signature yet — use an unguessable path or a query token you validate, and only accept HTTPS.
app.post("/heyrik/results", express.json(), async (req, res) => {
const ev = req.body;
res.sendStatus(200); // ack first — HeyRik times out at 12 s
if (ev.event === "contact_completed") {
await queue.add("heyrik-result", {
key: `${ev.contact_id}:${ev.call_id}`, // idempotency
phone: ev.phone,
outcome: ev.outcome,
fields: ev.call?.extracted_variables ?? {},
recording: ev.call?.recording_url ?? null,
});
} else if (ev.event === "campaign_completed") {
await notifyOps(`Campaign ${ev.campaign_name} done: ${JSON.stringify(ev.counts)}`);
}
});Every campaign has its own ingest URL and token. Your CRM, form, or ad platform posts leads to it as they arrive; HeyRik dials them inside the calling window.
Open the campaign in the dashboard → Live leads webhook. The URL carries the campaign's own token (cin_…), so no account API key is needed — and a leaked token exposes only that one campaign.
?token=cin_… in the URL, the X-Campaign-Token header, or token in the JSON body — whichever your tool supports.
Rotate from the campaign page; the old token stops working immediately.
curl -X POST "https://server.heyrik.com/api/campaigns/cmp_4a19/ingest?token=cin_4ead0ca15938393bbb0eac35f6" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{ "phone": "+919876543210", "fullName": "Ravi Kumar", "company": "Acme", "priority": 5 },
{ "phone": "+919123456789", "fullName": "Asha Patel", "company": "Nimbus" }
]
}'{
"data": { "added": 2, "skipped": 0, "rounded": 0, "total": 252, "reopened": false }
}Post { "phone": "+91…", "name": "…" } without the contacts wrapper. Add "fast": true to a lead (or the batch) to dial it before everything else.
A campaign is continuous: posting to a completed campaign sets it running again and the new leads are dialled. Only a cancelled campaign refuses leads (409 campaign_closed).
Send phones as strings in E.164 (+919876543210). A value like 9.17993E+11 — Excel's rounded form — has lost digits; it is counted in rounded and skipped, never dialled.
Every field you send with a lead is available to the agent as a {{variable}}. You do not have to match the agent's names.
An agent declares input variables — say prospect_name,prospect_company, industry_segment. When a campaign dials a lead, HeyRik maps the lead's fields to those variables by meaning: a field called fullName, Decision Maker or Contact person all fill prospect_name. The mapping is worked out once per campaign (an AI reads the field names and a few sample values), stored on the campaign, and applied to every lead — uploaded, posted to the webhook, or added by hand. A field named exactly like the variable is used as-is.
{
"phone": "+919876543210",
"Decision Maker": "Aju Thomas",
"Company": "CIRIL",
"Segment": "Real Estate & Construction",
"Role / Headline": "Corporate Real Estate Professional",
"Location": "Hyderabad, Telangana, India"
}prospect_name = Aju Thomas
prospect_company = CIRIL
industry_segment = Real Estate & Construction
prospect_role = Corporate Real Estate Professional
prospect_location = Hyderabad, Telangana, India
agent_name = (the variable's default)What each status means for your integration.
Created; nothing dialled yet.
POST /campaigns/{id}/start to beginscheduledStarts itself at schedule_at.
PATCH schedule_at: null to start nowrunningDialling inside call_window_start–end on call_days; retries per max_retries.
Push leads any time — next tick picks them uppausedHolds; the in-flight call finishes.
POST /start resumescompletedNothing left to dial — but not closed.
New leads reopen it automaticallycancelledTerminal. Results are kept; leads are refused.
Create a new campaignEverything the webhook delivers is also readable, so a batch job can reconcile without receiving anything.
# progress + counts
curl https://server.heyrik.com/api/v1/campaigns/cmp_4a19 -H "Authorization: Bearer $HEYRIK_API_KEY"
# every finished lead with outcome, category, extracted fields
curl "https://server.heyrik.com/api/v1/campaigns/cmp_4a19/contacts?status=completed&limit=500" \
-H "Authorization: Bearer $HEYRIK_API_KEY"
# one lead with its recording + transcript
curl https://server.heyrik.com/api/v1/campaigns/cmp_4a19/contacts/cct_9b1e -H "Authorization: Bearer $HEYRIK_API_KEY"
# or the whole run as a CSV
curl https://server.heyrik.com/api/v1/campaigns/cmp_4a19/export -H "Authorization: Bearer $HEYRIK_API_KEY" -o results.csvapi_key_requiredNo key, or a revoked one. Create a new key in the dashboard.
insufficient_creditsTop up before starting a run; `need` tells you the minimum.
campaign_not_foundWrong id, or a campaign owned by another account.
campaign_closedThe campaign was cancelled — create a new one.
rate_limitedBack off with jitter and retry.
Ten minutes of checks that prevent a bad first campaign.