Skip to main content
POST
Launch campaign
Moves a campaign to running so it starts offering calls. A preflight runs first: if anything would stop the campaign dialing, launch is refused with 422 CAMPAIGN_NOT_READY and a message naming the specific problem, rather than accepting the launch and going quiet. Launch works from draft and from any paused status, so it doubles as a resume. POST /campaigns/{id}/resume is the same operation with a different audit label. Required scope: campaigns:manage.

Path parameters

There is no request body. Everything the launch needs is already on the campaign — configure it with POST /campaigns or PATCH /campaigns/{id} first.

Preflight checklist

All ten must hold. The first failure is the one you get back. No configuration field has a default, so anything you never sent is still null and this check names it: max_attempts, max_infra_retries, retry_no_answer_seconds, retry_completed_seconds, randomize_retry_time, stop_on_no_answer, stop_on_voicemail, stop_on_unclassified, double_dial_enabled, double_dial_gap_seconds, max_calls_per_day, min_seconds_between_calls, max_in_flight. Fill them with PATCH /campaigns/{id} and launch again. An agent with max_call_duration_secs = 0 has no cap of its own: each call can run up to the platform’s 65-minute limit, which puts worst-case spend far above any budget and silently turns budget_cents into a no-op. That is why a positive cap is mandatory for campaigns specifically.

The full launch sequence

What happens after launch

  • The campaign is evaluated roughly once a minute. Each evaluation checks credit, workspace calling hours, max_calls_per_day, min_seconds_between_calls, max_in_flight and budget_cents, then starts as many calls as those limits allow.
  • Each call is an ordinary outbound call on the shared queue, with the same weight as one placed by POST /calls. Pacing controls how fast a campaign offers calls; it never grants priority.
  • started_at is stamped on the first launch and preserved across later pause/resume cycles.
  • last_tick_result explains every quiet minute — spacing, daily_cap_reached, outside_call_window, max_in_flight, no_eligible_leads, and so on. It is the first thing to read when a running campaign appears to be doing nothing. Full vocabulary on the stats page.
  • The campaign auto-completes (status: "completed", completed_at set) once no contact is in a live state.
The engine may pause a running campaign on its own: paused_insufficient_credit (balance below the minimum needed to place a call, auto-resumes on top-up), paused_budget, paused_infra (transient capacity problem) or paused_config (inactive from-number, agent duration cap removed, no reachable calling window).

Already running

Launching a campaign that is already running is not an error. The campaign is returned unchanged with an extra field:

Example response

HTTP 200 with the full campaign object, same shape as GET /campaigns/{id}:
Trimmed for brevity — the real response contains every field listed on GET /campaigns/{id}.

Not-ready response

Fix the named problem with PATCH /campaigns/{id} (or POST /campaigns/{id}/leads, or your calling hours) and call launch again. Nothing about the campaign changes on a refused launch.

Errors

Authorizations

Authorization
string
header
required

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

Path Parameters

id
string<uuid>
required

Response

Campaign is running

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.