Heppu APIv2.heppu.ai/api

Schedule a call

Dial later, with retries.

POST/api/v1/calls/schedulekey or session

Older than jobs and still popular with Make and Zapier users. Puts one call in the queue at a time you pick, with an optional retry plan.

Since 2026-09 the call runs on the job runtime like everything else: we resolve (and create on first use) a job named Scheduled calls - <worker name> for the worker and queue your contact against it. Edit that job in the dashboard to give these calls steps, branches and outcomes. The dial happens on the next scheduler tick rather than the instant you post - about a minute.

Your retry plan becomes that job's attempt ladder, so a worker + caller id + ladder combination gets its own job. Retries still fire on no-answer and busy, the same two outcomes as before.

agentIduuidrequired
Voice worker with a phone number.
contactPhonestringrequired
E.164.
contactNamestring
contactEmailstring
callerIdstring
One of the worker's numbers.
scheduledAtISO 8601
Omit to call now. Past times become "now".
priorityint
Accepted, no longer read. It ordered the old queue; contacts now dispatch oldest-waiting first.default 0
maxRetriesint
Defaults to the length of retrySchedule or retryIntervals if given.default 0
retryScheduleISO 8601[]
Exact retry times. We convert them to gaps when you post - the first measured from the call's start time, each later one from the entry before it - so a retry lands a little after your timestamp rather than exactly on it.
retryIntervals{ value, unit }[]
Relative, measured from the end of the previous attempt. unit is minutes, hours or days.
callMetadataobject
Contact and context for the worker. firstName/lastName here fill contactName if missing.
metadataobject
Yours. Echoed in webhooks.
curl -X POST https://v2.heppu.ai/api/v1/calls/schedule \
  -H "x-api-key: hep_..." -H "Content-Type: application/json" \
  -d '{
    "agentId": "6f0c…",
    "contactPhone": "+358401234567",
    "contactName": "Ada",
    "scheduledAt": "2026-09-12T09:00:00+03:00",
    "retryIntervals": [{ "value": 2, "unit": "hours" }, { "value": 1, "unit": "days" }]
  }'
201
{
  "data": {
    "id": "…",
    "status": "scheduled",
    "scheduledAt": "2026-09-12T06:00:00.000Z",
    "agent": { "id": "…", "name": "…" },
    "contact": { "phone": "+358401234567", "name": "Ada", "email": null }
  },
  "meta": { "timestamp": "…", "message": "Call scheduled successfully" }
}

We strip zero-width and control characters from the body before parsing, because Make.com likes to sneak them in. If you still get Invalid JSON payload, check your quotes.

403 blocked number. 400 not a voice worker, no phone number, bad callerId, bad scheduledAt.

A 400 can also come from the job behind the endpoint rather than from your request. If that job was edited into a shape the runtime cannot start, we refuse the call instead of queueing one that nothing will dial, and error.code says which column to fix:

error.codeFix
schema_version_not_v3The job is still on the old step format. Open it in the dashboard and save it once.
empty_process_stepsEvery step was deleted. Give it at least one.
pre_steps_unsupportedIt still carries executionConfig.preSteps, the retired pre-call SMS/wait chain. Re-author those as process steps and clear preSteps.

A paused, archived, draft or limit-reached job is brought back for you.

Two other job states refuse the call without a 400: a job you deleted, and a job left in a status that cannot be moved back to running (completed, failed - reachable by a restored backup or a hand-edited row, not from the dashboard). Both answer 500 with the generic Internal server error body and no code. If a schedule call that used to work starts returning 500, that is the first thing to check: restore or recreate the job, or point the request at a job of your own.