Skip to main content
POST
Create campaign
A campaign is a paced list of outbound calls over enrolled contacts. You describe who to call, which agent calls them, when to stop calling a person, and how fast the list may be worked; the platform does the rest. A campaign is always created in draft, whatever you send. Nothing is dialed until you call POST /campaigns/{id}/launch, so a draft is a safe place to enroll contacts and tune pacing. Required scope: campaigns:manage.
No configuration field has a default. The “Suggested” column below is what the dashboard prefills for a human, not what the API substitutes for you. A field you never send stays null, and POST /campaigns/{id}/launch returns 422 CAMPAIGN_NOT_READY naming it.This is deliberate. How many times to call someone, and when to stop, are decisions the platform will not quietly make on your behalf. Send every field on create, or fill the gaps with PATCH before you launch.

The full journey

  1. Create the draft — POST /campaigns with a name, the agent_id that will do the talking, the from_phone_number_id to call from, a regulatory_basis, and at least one stop rule.
  2. Enroll contacts — POST /campaigns/{id}/leads with lead_ids (leads you already have) and/or phone_numbers (numbers that become leads). Numbers on the do-not-call list are reported and skipped.
  3. Tune it — PATCH /campaigns/{id} for pacing, attempt caps, stop rules, budget. Re-read with GET /campaigns/{id}.
  4. Launch — POST /campaigns/{id}/launch. A preflight runs first and returns 422 CAMPAIGN_NOT_READY with a specific reason when the campaign cannot dial. On success the status becomes running.
  5. Watch it — GET /campaigns/{id}/stats for progress counters and last_tick_result, the machine-readable answer to “why is nothing happening right now”. GET /campaigns/{id}/leads for per-contact state.
  6. Control it — POST /campaigns/{id}/pause, /resume, /stop (terminal), or DELETE /campaigns/{id} to archive.

A campaign call is an ordinary outbound call

A campaign hands calls to the same outbound queue that POST /calls uses, at the same weight. The pacing fields below control how fast a campaign offers calls to that queue — they never buy priority over anything else in the workspace and they cannot raise your concurrency. If the queue is busy, a campaign waits its turn like any other call. Dialing is also gated by your workspace calling hours (GET /call-windows) and by your credit balance, both re-checked continuously while the campaign runs.

Identity and targeting

Stop rules — when to stop calling one person

Two independent per-contact stop conditions apply, whichever fires first:
  • max_attempts — the attempt cap.
  • The stop-disposition set — landing one of these outcomes retires that contact permanently. Anything else is retried until the cap.
At least one of stop_disposition_ids, stop_on_no_answer or stop_on_voicemail must be set before launch. A campaign with no stop rule would call every contact until the attempt cap, so the preflight refuses it. To stop calling people you have already spoken to, create a disposition for that outcome (POST /dispositions) and put its ID in stop_disposition_ids. There is no built-in “reached a human” rule: what counts as a real conversation differs per workspace, so it belongs in your own outcome list where you can see and change it. Do not put No Answer, Failed, Voicemail or Unclassified in stop_disposition_ids. Those labels are also assigned automatically, and a real 90-second conversation can legitimately end up carrying one of them — putting them in the set would retire genuine conversations as never-reached. Use the stop_on_no_answer / stop_on_voicemail / stop_on_unclassified booleans instead: they are evaluated on the call outcome rather than on the label. Categorization happens after a call ends, not during it, so a contact sits in awaiting_disposition until its outcome lands. It is never re-dialed while in that state, and there is no deadline after which the platform decides without one.
A call’s outcome is authoritative, and a contact is never advanced without one. Outcomes are assigned asynchronously after a call ends, so a contact sits in awaiting_disposition until its outcome arrives — however long that takes. Only that contact waits; the pacer keeps admitting every other contact in the campaign. There is no timeout that decides on your behalf, because “we never learned what happened” is not a basis for calling someone again.

Attempts and retries

Pacing

Money and schedule

Fields you cannot set

Unknown keys are rejected with 400 rather than ignored, so a typo like stop_dispositions can never leave you believing you configured a kill switch that is not there. retry_rules is rejected too. It was an earlier structured retry matrix that the dialer never evaluated; the scalar fields above are the whole retry configuration. These are engine-owned and rejected the same way: status, from_number, daily_admitted_count, daily_window_date, last_admitted_at, spent_cents, reserved_cents, estimate_cents, last_tick_at, last_tick_result, last_error, started_at, completed_at, total_leads, stats.

Example request

Example response

HTTP 201. agent, from_phone_number and stop_dispositions expand the IDs you supplied — each is the complete referenced object, abbreviated here for brevity.

Errors

Authorizations

Authorization
string
header
required

Your Yappr API key (e.g. ypr_live_...). Generate one in the dashboard under Settings → API Keys.

Body

application/json
name
string
required
Example:

"July reactivation"

description
string | null
agent_id
string<uuid>

Required before launch.

from_phone_number_id
string<uuid>

Required before launch.

retry_rules
object
calling_window
object

Optional narrowing of the workspace calling hours. Cannot widen them.

stop_disposition_ids
string<uuid>[]

Disposition IDs (never labels) that retire a contact permanently. Do not include No Answer, Failed or Voicemail — use stop_on_no_answer / stop_on_voicemail instead.

stop_on_no_answer
boolean
Example:

false

stop_on_voicemail
boolean
Example:

false

stop_on_unclassified
boolean
Example:

false

max_attempts
integer
Required range: 1 <= x <= 999
Example:

3

max_infra_retries
integer
Required range: 0 <= x <= 20
Example:

3

retry_no_answer_seconds
integer
Required range: 30 <= x <= 604800
Example:

3600

retry_completed_seconds
integer
Required range: 60 <= x <= 604800
Example:

86400

randomize_retry_time
boolean

false keeps a retry at the same time of day; true picks another hour inside the calling window.

Example:

false

double_dial_enabled
boolean
Example:

false

double_dial_gap_seconds
integer
Required range: 10 <= x <= 3600
Example:

90

max_calls_per_day
integer
Required range: 1 <= x <= 100000
Example:

200

min_seconds_between_calls
integer
Required range: 0 <= x <= 86400
Example:

30

max_in_flight
integer
Required range: 1 <= x <= 8
Example:

2

budget_cents
integer | null

Hard spend cap. Null for no cap.

Required range: x >= 1
regulatory_basis
enum<string>

Required before launch.

Available options:
lawful_basis_confirmed,
consent,
existing_customer,
non_marketing,
registry_screened
starts_at
string<date-time> | null
ends_at
string<date-time> | null

Response

Campaign created in draft

A paced bulk-outbound dialing job over enrolled contacts.

A campaign is an admission-only scheduler. It decides when each enrolled contact becomes eligible, then performs one ordinary outbound call — the same queue, the same priority, and the same concurrency limits as a call placed with POST /calls. The pacing fields below only control how fast a campaign hands calls to that queue; a campaign call never takes precedence over anything else.

Campaigns are always created as draft and are launched with POST /campaigns/{id}/launch.

Two independent per-contact stop conditions apply, whichever fires first: max_attempts, and the stop-disposition set (stop_disposition_ids plus the stop_on_* booleans). Landing a stop disposition retires that contact permanently; any other outcome retries until the attempt cap.

Fields marked Engine-owned are written by the campaign engine and are never writable. Sending one on create or update returns 400, as does any field name that isn't on the writable list — a misspelled stop_dispositions is rejected rather than silently ignored, so you can never believe a kill switch is armed when it is not.

id
string<uuid>
company_id
string<uuid>
name
string
Example:

"July reactivation"

description
string | null
status
enum<string>

Engine-owned. Move it with the transition endpoints (launch / pause / resume / stop), never with PATCH.

draft — created, not dialing. scheduled — waiting for starts_at. running — admitting contacts. paused — paused by you. A manual pause is never lifted automatically; it survives a top-up. paused_insufficient_credit — balance is below the minimum needed to place a call. Auto-resumes shortly after the balance is topped up. paused_budget — budget_cents reached. paused_infra — repeated platform-side failures. paused_config — a permanent configuration problem (from-number no longer active, agent missing a call-duration cap, no reachable calling window). completed — every enrolled contact reached a terminal state. stopped — stopped by you. Terminal. archived — soft-deleted by DELETE /campaigns/{id}. Terminal.

Available options:
draft,
scheduled,
running,
paused,
paused_insufficient_credit,
paused_budget,
paused_infra,
paused_config,
completed,
stopped,
archived
agent_id
string<uuid> | null

Agent that runs the calls. Required before launch.

agent
object | null

Full Agent object for agent_id.

from_phone_number_id
string<uuid> | null

Number the campaign calls from. Required before launch, and must still be active at launch time.

from_phone_number
object | null

Full PhoneNumber object for from_phone_number_id.

from_number
string | null

Engine-owned. E.164 snapshot of the from-number, kept for audit even if the number is later released.

Example:

"+972551234567"

retry_rules
object

Optional per-outcome retry overrides. Free-form object — the explicit max_attempts / retry_*_seconds fields cover the common cases.

calling_window
object

Optional campaign-level narrowing of the workspace calling hours. Omit to inherit the workspace schedule (GET /call-windows). A campaign can only ever dial inside the workspace window — this field cannot widen it.

stop_disposition_ids
string<uuid>[]

Disposition IDs that retire a contact permanently when assigned — the campaign's kill switch. Always IDs, never labels: labels are renameable, and a rename would silently disarm the rule.

Do not list No Answer, Failed or Voicemail here. Those three are also auto-assigned to calls nobody picked up, so using them as stop dispositions retires contacts you did in fact speak to. Use stop_on_no_answer and stop_on_voicemail instead.

Must contain dispositions belonging to your company; anything else is rejected with 400.

stop_dispositions
object[]

Full Disposition objects for stop_disposition_ids.

stop_on_no_answer
boolean

Retire a contact the first time a call goes unanswered.

Example:

false

stop_on_voicemail
boolean

Retire a contact the first time a call reaches an answering machine.

Example:

false

stop_on_unclassified
boolean

What to do when the outcome that arrives is Unclassified — the call happened but could not be matched to any of your outcomes. false retries the contact, true retires it. This is not a timeout. A call's outcome is authoritative and a contact is never advanced without one: if classification is slow, only that contact waits while the campaign keeps calling everyone else.

Example:

false

max_attempts
integer

Per-contact dial cap. One of the two independent stop conditions — the other is the stop-disposition set.

Required range: 1 <= x <= 999
Example:

3

max_infra_retries
integer

Separate budget for retries after a platform-side failure — a call that never reached the person. These do not consume max_attempts and are never terminal.

Required range: 0 <= x <= 20
Example:

3

retry_no_answer_seconds
integer

Delay before retrying a contact who did not answer.

Required range: 30 <= x <= 604800
Example:

3600

retry_completed_seconds
integer

Delay before retrying a contact whose call completed but landed on a non-stop disposition.

Required range: 60 <= x <= 604800
Example:

86400

randomize_retry_time
boolean

Which time of day a retry lands on. false keeps the wait exact, so a one-week wait retries at the same hour a week later. true picks a different hour inside the campaign's calling window, so repeat attempts do not always arrive at the same moment. The wait length itself is unchanged either way — a randomized retry is still never earlier than the configured wait.

Example:

false

double_dial_enabled
boolean

Ring a second time shortly after an unanswered first ring.

Example:

false

double_dial_gap_seconds
integer

Gap between the two rings when double_dial_enabled is true.

Required range: 10 <= x <= 3600
Example:

90

max_calls_per_day
integer

Daily admission cap, counted against the workspace timezone day.

Required range: 1 <= x <= 100000
Example:

200

min_seconds_between_calls
integer

Minimum spacing between two calls handed to the queue by this campaign.

Required range: 0 <= x <= 86400
Example:

30

max_in_flight
integer

Calls this campaign may have live at once. Platform concurrency limits still apply on top — raising this does not buy the campaign extra capacity.

Required range: 1 <= x <= 8
Example:

2

budget_cents
integer | null

Hard spend cap for the campaign, enforced against spent_cents + reserved_cents (credits are debited when a call ends, so in-flight cost has to count). null means no cap. On hit the campaign moves to paused_budget.

Required range: x >= 1
regulatory_basis
enum<string> | null

Your lawful basis for calling this list. Required before launch — recorded on the campaign's launch audit record alongside the enrolled count. lawful_basis_confirmed is what the dashboard records: a single attestation that the caller has consent or another lawful basis for everyone on the list. The four specific values are for callers that know which one applies.

Available options:
lawful_basis_confirmed,
consent,
existing_customer,
non_marketing,
registry_screened
starts_at
string<date-time> | null

Do not admit contacts before this time.

ends_at
string<date-time> | null

Do not admit contacts after this time.

daily_admitted_count
integer

Engine-owned. Calls handed to the queue during the current day window.

daily_window_date
string<date> | null

Engine-owned. Day daily_admitted_count belongs to.

last_admitted_at
string<date-time> | null

Engine-owned. When this campaign last handed a call to the queue.

estimate_cents
integer | null

Engine-owned. Projected campaign cost at the time of launch.

spent_cents
integer

Engine-owned. Settled spend so far.

reserved_cents
integer

Engine-owned. Worst-case cost of in-flight calls, held against budget_cents until they settle.

last_tick_at
string<date-time> | null

Engine-owned. Last time the engine evaluated this campaign.

last_tick_result
string | null

Engine-owned. Machine-readable answer to "why is nothing happening right now" — e.g. daily cap reached, outside the calling window, waiting on in-flight calls.

last_error
string | null

Engine-owned. Last error the engine recorded for this campaign.

started_at
string<date-time> | null

Engine-owned. First launch time.

completed_at
string<date-time> | null

Engine-owned. When the last contact reached a terminal state.

total_leads
integer

Engine-owned. Contacts currently enrolled.

stats
object

Engine-owned counter roll-up. Prefer GET /campaigns/{id}/stats.

created_at
string<date-time>
updated_at
string<date-time>
created_by
string<uuid> | null

Null when the campaign was created via the API.