curl --request POST \
--url https://api.goyappr.com/campaigns \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "July reactivation",
"description": "<string>",
"agent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"from_phone_number_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry_rules": {},
"calling_window": {},
"stop_disposition_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"stop_on_no_answer": false,
"stop_on_voicemail": false,
"stop_on_unclassified": false,
"max_attempts": 3,
"max_infra_retries": 3,
"retry_no_answer_seconds": 3600,
"retry_completed_seconds": 86400,
"randomize_retry_time": false,
"double_dial_enabled": false,
"double_dial_gap_seconds": 90,
"max_calls_per_day": 200,
"min_seconds_between_calls": 30,
"max_in_flight": 2,
"budget_cents": 2,
"starts_at": "2023-11-07T05:31:56Z",
"ends_at": "2023-11-07T05:31:56Z"
}
'import requests
url = "https://api.goyappr.com/campaigns"
payload = {
"name": "July reactivation",
"description": "<string>",
"agent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"from_phone_number_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry_rules": {},
"calling_window": {},
"stop_disposition_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"stop_on_no_answer": False,
"stop_on_voicemail": False,
"stop_on_unclassified": False,
"max_attempts": 3,
"max_infra_retries": 3,
"retry_no_answer_seconds": 3600,
"retry_completed_seconds": 86400,
"randomize_retry_time": False,
"double_dial_enabled": False,
"double_dial_gap_seconds": 90,
"max_calls_per_day": 200,
"min_seconds_between_calls": 30,
"max_in_flight": 2,
"budget_cents": 2,
"starts_at": "2023-11-07T05:31:56Z",
"ends_at": "2023-11-07T05:31:56Z"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'July reactivation',
description: '<string>',
agent_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
from_phone_number_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
retry_rules: {},
calling_window: {},
stop_disposition_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
stop_on_no_answer: false,
stop_on_voicemail: false,
stop_on_unclassified: false,
max_attempts: 3,
max_infra_retries: 3,
retry_no_answer_seconds: 3600,
retry_completed_seconds: 86400,
randomize_retry_time: false,
double_dial_enabled: false,
double_dial_gap_seconds: 90,
max_calls_per_day: 200,
min_seconds_between_calls: 30,
max_in_flight: 2,
budget_cents: 2,
starts_at: '2023-11-07T05:31:56Z',
ends_at: '2023-11-07T05:31:56Z'
})
};
fetch('https://api.goyappr.com/campaigns', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.goyappr.com/campaigns",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'July reactivation',
'description' => '<string>',
'agent_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'from_phone_number_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'retry_rules' => [
],
'calling_window' => [
],
'stop_disposition_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
],
'stop_on_no_answer' => false,
'stop_on_voicemail' => false,
'stop_on_unclassified' => false,
'max_attempts' => 3,
'max_infra_retries' => 3,
'retry_no_answer_seconds' => 3600,
'retry_completed_seconds' => 86400,
'randomize_retry_time' => false,
'double_dial_enabled' => false,
'double_dial_gap_seconds' => 90,
'max_calls_per_day' => 200,
'min_seconds_between_calls' => 30,
'max_in_flight' => 2,
'budget_cents' => 2,
'starts_at' => '2023-11-07T05:31:56Z',
'ends_at' => '2023-11-07T05:31:56Z'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.goyappr.com/campaigns"
payload := strings.NewReader("{\n \"name\": \"July reactivation\",\n \"description\": \"<string>\",\n \"agent_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"from_phone_number_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"retry_rules\": {},\n \"calling_window\": {},\n \"stop_disposition_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"stop_on_no_answer\": false,\n \"stop_on_voicemail\": false,\n \"stop_on_unclassified\": false,\n \"max_attempts\": 3,\n \"max_infra_retries\": 3,\n \"retry_no_answer_seconds\": 3600,\n \"retry_completed_seconds\": 86400,\n \"randomize_retry_time\": false,\n \"double_dial_enabled\": false,\n \"double_dial_gap_seconds\": 90,\n \"max_calls_per_day\": 200,\n \"min_seconds_between_calls\": 30,\n \"max_in_flight\": 2,\n \"budget_cents\": 2,\n \"starts_at\": \"2023-11-07T05:31:56Z\",\n \"ends_at\": \"2023-11-07T05:31:56Z\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.goyappr.com/campaigns")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"July reactivation\",\n \"description\": \"<string>\",\n \"agent_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"from_phone_number_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"retry_rules\": {},\n \"calling_window\": {},\n \"stop_disposition_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"stop_on_no_answer\": false,\n \"stop_on_voicemail\": false,\n \"stop_on_unclassified\": false,\n \"max_attempts\": 3,\n \"max_infra_retries\": 3,\n \"retry_no_answer_seconds\": 3600,\n \"retry_completed_seconds\": 86400,\n \"randomize_retry_time\": false,\n \"double_dial_enabled\": false,\n \"double_dial_gap_seconds\": 90,\n \"max_calls_per_day\": 200,\n \"min_seconds_between_calls\": 30,\n \"max_in_flight\": 2,\n \"budget_cents\": 2,\n \"starts_at\": \"2023-11-07T05:31:56Z\",\n \"ends_at\": \"2023-11-07T05:31:56Z\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.goyappr.com/campaigns")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"July reactivation\",\n \"description\": \"<string>\",\n \"agent_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"from_phone_number_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"retry_rules\": {},\n \"calling_window\": {},\n \"stop_disposition_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"stop_on_no_answer\": false,\n \"stop_on_voicemail\": false,\n \"stop_on_unclassified\": false,\n \"max_attempts\": 3,\n \"max_infra_retries\": 3,\n \"retry_no_answer_seconds\": 3600,\n \"retry_completed_seconds\": 86400,\n \"randomize_retry_time\": false,\n \"double_dial_enabled\": false,\n \"double_dial_gap_seconds\": 90,\n \"max_calls_per_day\": 200,\n \"min_seconds_between_calls\": 30,\n \"max_in_flight\": 2,\n \"budget_cents\": 2,\n \"starts_at\": \"2023-11-07T05:31:56Z\",\n \"ends_at\": \"2023-11-07T05:31:56Z\"\n}"
response = http.request(request)
puts response.read_body{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"company_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "July reactivation",
"description": "<string>",
"status": "draft",
"agent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"agent": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"type": "prompt",
"flow_config": {
"nodes": [
{
"id": "<string>",
"type": "start",
"name": "<string>",
"position": {
"x": 123,
"y": 123
},
"agent_speaks_first": true,
"greeting": "<string>",
"is_literal": false,
"next_step_id": "<string>",
"auto_advance": true
}
],
"flow_config_version": "1",
"metadata": {
"custom_metadata_keys": [
"<string>"
]
}
},
"system_prompt": "<string>",
"description": "<string>",
"voice": "Michal",
"background_sound": "call_center",
"background_sound_volume": 0.3,
"language": "he",
"temperature": 1,
"greeting_message": "<string>",
"agent_speaks_first": true,
"vad_stop_secs": 0.5,
"vad_start_secs": 0.2,
"vad_confidence": 0.7,
"silence_timeout_secs": 60,
"max_continuous_speech_secs": 120,
"max_call_duration_secs": 600,
"lead_memory_enabled": true,
"is_active": true,
"webhook_url": "<string>",
"webhook_events": [
"call.started"
],
"webhook_headers": {
"Authorization": "Bearer sk_live_…",
"X-Source": "yappr"
},
"extraction_parameters": [
{
"name": "customerName",
"description": "The caller's full name as mentioned during the conversation"
}
],
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z"
},
"from_phone_number_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"from_phone_number": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"number": "+972551234567",
"friendly_name": "<string>",
"provider": "<string>",
"status": "active",
"is_active": true,
"inbound_agent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"outbound_agent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"sip_inbound_configured": true,
"sip_outbound_configured": true,
"country_code": "<string>",
"monthly_cost": 123,
"created_at": "2023-11-07T05:31:56Z",
"carrier_account": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"provider": "telnyx",
"status": "untested"
},
"ownership_verified_at": "2023-11-07T05:31:56Z"
},
"from_number": "+972551234567",
"retry_rules": {},
"calling_window": {},
"stop_disposition_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"stop_dispositions": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"label": "<string>",
"color": "#22c55e",
"position": 123,
"is_protected": true,
"created_at": "2023-11-07T05:31:56Z"
}
],
"stop_on_no_answer": false,
"stop_on_voicemail": false,
"stop_on_unclassified": false,
"max_attempts": 3,
"max_infra_retries": 3,
"retry_no_answer_seconds": 3600,
"retry_completed_seconds": 86400,
"randomize_retry_time": false,
"double_dial_enabled": false,
"double_dial_gap_seconds": 90,
"max_calls_per_day": 200,
"min_seconds_between_calls": 30,
"max_in_flight": 2,
"budget_cents": 2,
"regulatory_basis": "lawful_basis_confirmed",
"starts_at": "2023-11-07T05:31:56Z",
"ends_at": "2023-11-07T05:31:56Z",
"daily_admitted_count": 123,
"daily_window_date": "2023-12-25",
"last_admitted_at": "2023-11-07T05:31:56Z",
"estimate_cents": 123,
"spent_cents": 123,
"reserved_cents": 123,
"last_tick_at": "2023-11-07T05:31:56Z",
"last_tick_result": "<string>",
"last_error": "<string>",
"started_at": "2023-11-07T05:31:56Z",
"completed_at": "2023-11-07T05:31:56Z",
"total_leads": 123,
"stats": {},
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"created_by": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}Create a campaign
Creates the campaign in draft. Nothing is dialed until you enroll contacts and
call POST /campaigns/{id}/launch. Required scope campaigns:manage.
Only the fields below may be sent. Any other key — including every engine-owned
field (status, spent_cents, daily_admitted_count, last_tick_result, …) and
every typo — is rejected with 400 listing the writable set.
None of the configuration fields have defaults. The example values show what
the dashboard prefills, but a field you omit stays null and launch refuses with
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 make on your behalf.
Send every field on create, or fill the gaps with PATCH before launching.
curl --request POST \
--url https://api.goyappr.com/campaigns \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "July reactivation",
"description": "<string>",
"agent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"from_phone_number_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry_rules": {},
"calling_window": {},
"stop_disposition_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"stop_on_no_answer": false,
"stop_on_voicemail": false,
"stop_on_unclassified": false,
"max_attempts": 3,
"max_infra_retries": 3,
"retry_no_answer_seconds": 3600,
"retry_completed_seconds": 86400,
"randomize_retry_time": false,
"double_dial_enabled": false,
"double_dial_gap_seconds": 90,
"max_calls_per_day": 200,
"min_seconds_between_calls": 30,
"max_in_flight": 2,
"budget_cents": 2,
"starts_at": "2023-11-07T05:31:56Z",
"ends_at": "2023-11-07T05:31:56Z"
}
'import requests
url = "https://api.goyappr.com/campaigns"
payload = {
"name": "July reactivation",
"description": "<string>",
"agent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"from_phone_number_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry_rules": {},
"calling_window": {},
"stop_disposition_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"stop_on_no_answer": False,
"stop_on_voicemail": False,
"stop_on_unclassified": False,
"max_attempts": 3,
"max_infra_retries": 3,
"retry_no_answer_seconds": 3600,
"retry_completed_seconds": 86400,
"randomize_retry_time": False,
"double_dial_enabled": False,
"double_dial_gap_seconds": 90,
"max_calls_per_day": 200,
"min_seconds_between_calls": 30,
"max_in_flight": 2,
"budget_cents": 2,
"starts_at": "2023-11-07T05:31:56Z",
"ends_at": "2023-11-07T05:31:56Z"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'July reactivation',
description: '<string>',
agent_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
from_phone_number_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
retry_rules: {},
calling_window: {},
stop_disposition_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
stop_on_no_answer: false,
stop_on_voicemail: false,
stop_on_unclassified: false,
max_attempts: 3,
max_infra_retries: 3,
retry_no_answer_seconds: 3600,
retry_completed_seconds: 86400,
randomize_retry_time: false,
double_dial_enabled: false,
double_dial_gap_seconds: 90,
max_calls_per_day: 200,
min_seconds_between_calls: 30,
max_in_flight: 2,
budget_cents: 2,
starts_at: '2023-11-07T05:31:56Z',
ends_at: '2023-11-07T05:31:56Z'
})
};
fetch('https://api.goyappr.com/campaigns', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.goyappr.com/campaigns",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'July reactivation',
'description' => '<string>',
'agent_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'from_phone_number_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'retry_rules' => [
],
'calling_window' => [
],
'stop_disposition_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
],
'stop_on_no_answer' => false,
'stop_on_voicemail' => false,
'stop_on_unclassified' => false,
'max_attempts' => 3,
'max_infra_retries' => 3,
'retry_no_answer_seconds' => 3600,
'retry_completed_seconds' => 86400,
'randomize_retry_time' => false,
'double_dial_enabled' => false,
'double_dial_gap_seconds' => 90,
'max_calls_per_day' => 200,
'min_seconds_between_calls' => 30,
'max_in_flight' => 2,
'budget_cents' => 2,
'starts_at' => '2023-11-07T05:31:56Z',
'ends_at' => '2023-11-07T05:31:56Z'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.goyappr.com/campaigns"
payload := strings.NewReader("{\n \"name\": \"July reactivation\",\n \"description\": \"<string>\",\n \"agent_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"from_phone_number_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"retry_rules\": {},\n \"calling_window\": {},\n \"stop_disposition_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"stop_on_no_answer\": false,\n \"stop_on_voicemail\": false,\n \"stop_on_unclassified\": false,\n \"max_attempts\": 3,\n \"max_infra_retries\": 3,\n \"retry_no_answer_seconds\": 3600,\n \"retry_completed_seconds\": 86400,\n \"randomize_retry_time\": false,\n \"double_dial_enabled\": false,\n \"double_dial_gap_seconds\": 90,\n \"max_calls_per_day\": 200,\n \"min_seconds_between_calls\": 30,\n \"max_in_flight\": 2,\n \"budget_cents\": 2,\n \"starts_at\": \"2023-11-07T05:31:56Z\",\n \"ends_at\": \"2023-11-07T05:31:56Z\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.goyappr.com/campaigns")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"July reactivation\",\n \"description\": \"<string>\",\n \"agent_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"from_phone_number_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"retry_rules\": {},\n \"calling_window\": {},\n \"stop_disposition_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"stop_on_no_answer\": false,\n \"stop_on_voicemail\": false,\n \"stop_on_unclassified\": false,\n \"max_attempts\": 3,\n \"max_infra_retries\": 3,\n \"retry_no_answer_seconds\": 3600,\n \"retry_completed_seconds\": 86400,\n \"randomize_retry_time\": false,\n \"double_dial_enabled\": false,\n \"double_dial_gap_seconds\": 90,\n \"max_calls_per_day\": 200,\n \"min_seconds_between_calls\": 30,\n \"max_in_flight\": 2,\n \"budget_cents\": 2,\n \"starts_at\": \"2023-11-07T05:31:56Z\",\n \"ends_at\": \"2023-11-07T05:31:56Z\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.goyappr.com/campaigns")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"July reactivation\",\n \"description\": \"<string>\",\n \"agent_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"from_phone_number_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"retry_rules\": {},\n \"calling_window\": {},\n \"stop_disposition_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"stop_on_no_answer\": false,\n \"stop_on_voicemail\": false,\n \"stop_on_unclassified\": false,\n \"max_attempts\": 3,\n \"max_infra_retries\": 3,\n \"retry_no_answer_seconds\": 3600,\n \"retry_completed_seconds\": 86400,\n \"randomize_retry_time\": false,\n \"double_dial_enabled\": false,\n \"double_dial_gap_seconds\": 90,\n \"max_calls_per_day\": 200,\n \"min_seconds_between_calls\": 30,\n \"max_in_flight\": 2,\n \"budget_cents\": 2,\n \"starts_at\": \"2023-11-07T05:31:56Z\",\n \"ends_at\": \"2023-11-07T05:31:56Z\"\n}"
response = http.request(request)
puts response.read_body{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"company_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "July reactivation",
"description": "<string>",
"status": "draft",
"agent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"agent": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"type": "prompt",
"flow_config": {
"nodes": [
{
"id": "<string>",
"type": "start",
"name": "<string>",
"position": {
"x": 123,
"y": 123
},
"agent_speaks_first": true,
"greeting": "<string>",
"is_literal": false,
"next_step_id": "<string>",
"auto_advance": true
}
],
"flow_config_version": "1",
"metadata": {
"custom_metadata_keys": [
"<string>"
]
}
},
"system_prompt": "<string>",
"description": "<string>",
"voice": "Michal",
"background_sound": "call_center",
"background_sound_volume": 0.3,
"language": "he",
"temperature": 1,
"greeting_message": "<string>",
"agent_speaks_first": true,
"vad_stop_secs": 0.5,
"vad_start_secs": 0.2,
"vad_confidence": 0.7,
"silence_timeout_secs": 60,
"max_continuous_speech_secs": 120,
"max_call_duration_secs": 600,
"lead_memory_enabled": true,
"is_active": true,
"webhook_url": "<string>",
"webhook_events": [
"call.started"
],
"webhook_headers": {
"Authorization": "Bearer sk_live_…",
"X-Source": "yappr"
},
"extraction_parameters": [
{
"name": "customerName",
"description": "The caller's full name as mentioned during the conversation"
}
],
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z"
},
"from_phone_number_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"from_phone_number": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"number": "+972551234567",
"friendly_name": "<string>",
"provider": "<string>",
"status": "active",
"is_active": true,
"inbound_agent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"outbound_agent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"sip_inbound_configured": true,
"sip_outbound_configured": true,
"country_code": "<string>",
"monthly_cost": 123,
"created_at": "2023-11-07T05:31:56Z",
"carrier_account": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"provider": "telnyx",
"status": "untested"
},
"ownership_verified_at": "2023-11-07T05:31:56Z"
},
"from_number": "+972551234567",
"retry_rules": {},
"calling_window": {},
"stop_disposition_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"stop_dispositions": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"label": "<string>",
"color": "#22c55e",
"position": 123,
"is_protected": true,
"created_at": "2023-11-07T05:31:56Z"
}
],
"stop_on_no_answer": false,
"stop_on_voicemail": false,
"stop_on_unclassified": false,
"max_attempts": 3,
"max_infra_retries": 3,
"retry_no_answer_seconds": 3600,
"retry_completed_seconds": 86400,
"randomize_retry_time": false,
"double_dial_enabled": false,
"double_dial_gap_seconds": 90,
"max_calls_per_day": 200,
"min_seconds_between_calls": 30,
"max_in_flight": 2,
"budget_cents": 2,
"regulatory_basis": "lawful_basis_confirmed",
"starts_at": "2023-11-07T05:31:56Z",
"ends_at": "2023-11-07T05:31:56Z",
"daily_admitted_count": 123,
"daily_window_date": "2023-12-25",
"last_admitted_at": "2023-11-07T05:31:56Z",
"estimate_cents": 123,
"spent_cents": 123,
"reserved_cents": 123,
"last_tick_at": "2023-11-07T05:31:56Z",
"last_tick_result": "<string>",
"last_error": "<string>",
"started_at": "2023-11-07T05:31:56Z",
"completed_at": "2023-11-07T05:31:56Z",
"total_leads": 123,
"stats": {},
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"created_by": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}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.
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
- Create the draft —
POST /campaignswith aname, theagent_idthat will do the talking, thefrom_phone_number_idto call from, aregulatory_basis, and at least one stop rule. - Enroll contacts —
POST /campaigns/{id}/leadswithlead_ids(leads you already have) and/orphone_numbers(numbers that become leads). Numbers on the do-not-call list are reported and skipped. - Tune it —
PATCH /campaigns/{id}for pacing, attempt caps, stop rules, budget. Re-read withGET /campaigns/{id}. - Launch —
POST /campaigns/{id}/launch. A preflight runs first and returns422 CAMPAIGN_NOT_READYwith a specific reason when the campaign cannot dial. On success the status becomesrunning. - Watch it —
GET /campaigns/{id}/statsfor progress counters andlast_tick_result, the machine-readable answer to “why is nothing happening right now”.GET /campaigns/{id}/leadsfor per-contact state. - Control it —
POST /campaigns/{id}/pause,/resume,/stop(terminal), orDELETE /campaigns/{id}to archive.
A campaign call is an ordinary outbound call
A campaign hands calls to the same outbound queue thatPOST /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
| Field | Type | Suggested | Notes |
|---|---|---|---|
name | string | — | Required. Trimmed. Names do not have to be unique — “Follow-up” is a fine name for this month’s and last month’s, and a campaign is identified by its id everywhere that matters. |
description | string | null | Free text for your own bookkeeping. |
agent_id | uuid | null | The agent that places the calls. Required before launch. The agent must have a positive max_call_duration_secs so worst-case spend is bounded. |
from_phone_number_id | uuid | null | A number you own (GET /phone-numbers) and that is active. Required before launch. |
regulatory_basis | string | null | Required before launch. lawful_basis_confirmed (a single attestation that you have consent or another lawful basis — what the dashboard records), or one of the specific bases consent, existing_customer, non_marketing, registry_screened. Recorded with the launch event as the record of why these people were called. |
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.
| Field | Type | Suggested | Notes |
|---|---|---|---|
stop_disposition_ids | uuid[] | [] | Disposition IDs (GET /dispositions), never labels — labels are renameable, IDs are stable. Every ID must belong to your workspace. |
stop_on_no_answer | boolean | false | Retire a contact the first time nobody picks up. |
stop_on_voicemail | boolean | false | Retire a contact when the call reaches voicemail. |
stop_on_unclassified | boolean | false | What to do when the outcome that arrives is “no clear outcome” — the call happened but matched none of your outcomes. false retries, true retires. Not a timeout; see the note below. |
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.
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
| Field | Type | Suggested | Notes |
|---|---|---|---|
max_attempts | integer | 3 | 1–999. Dial attempts per contact before it becomes exhausted. Set it as high as your follow-up actually goes. |
max_infra_retries | integer | 3 | 0–20. A separate budget for failures where no call ever reached the person. These never consume a dial attempt and are never terminal for the contact. |
retry_no_answer_seconds | integer | 3600 | 30–604800. Wait before re-offering a contact nobody picked up. Also used when the call reached voicemail or a busy line. |
retry_completed_seconds | integer | 86400 | 60–604800. Wait before re-offering a contact whose call connected but did not hit a stop rule. |
randomize_retry_time | boolean | false | Which time of day the retry lands on. false keeps the wait exact — a one-week wait retries at the same hour a week later. true picks a different hour inside the calling window, so repeat attempts do not always arrive at the same moment. Either way the retry is never earlier than the configured wait. |
double_dial_enabled | boolean | false | Ring a second time shortly after an unanswered first ring. The pair counts as one attempt. |
double_dial_gap_seconds | integer | 90 | 10–3600. Gap between the two rings. Required before launch even when double_dial_enabled is false. |
Pacing
| Field | Type | Suggested | Notes |
|---|---|---|---|
max_calls_per_day | integer | 200 | 1–100000. Resets at midnight in your workspace timezone. |
min_seconds_between_calls | integer | 30 | 0–86400. Minimum spacing between two calls from this campaign. |
max_in_flight | integer | 2 | 1–8. How many calls this campaign may have outstanding at once. Self-restraint, not a reservation — it stops one campaign monopolising shared outbound capacity. |
calling_window | object | {} | Optional per-campaign narrowing, { tz, days: [0..6], start: "HH:MM", end: "HH:MM" }. Leave it {} to follow the workspace calling hours, which gate dialing either way. |
Money and schedule
| Field | Type | Suggested | Notes |
|---|---|---|---|
budget_cents | integer | null | null | Positive integer, or null for no cap. Enforced against spend plus the worst case of everything currently in flight, so a campaign cannot overshoot by an entire live cohort. On hit the campaign moves to paused_budget. |
starts_at | timestamp | null | ISO-8601. Records the intended start. It does not launch the campaign for you — a campaign only dials while it is running. |
ends_at | timestamp | null | ISO-8601. Records the intended end. |
Fields you cannot set
Unknown keys are rejected with 400 rather than ignored, so a typo likestop_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
curl -X POST "https://api.goyappr.com/campaigns" \
-H "Authorization: Bearer $YAPPR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "July renewals",
"description": "One call per renewal lead, up to three tries.",
"agent_id": "7e8a91c1-0000-4c11-9a00-000000000001",
"from_phone_number_id": "2d9f4b6a-0000-4a3c-8b21-000000000002",
"regulatory_basis": "existing_customer",
"stop_disposition_ids": [
"5c1d9a2e-0000-4b10-9f31-000000000010",
"5c1d9a2e-0000-4b10-9f31-000000000011"
],
"stop_on_no_answer": false,
"stop_on_voicemail": true,
"stop_on_unclassified": false,
"max_attempts": 3,
"max_infra_retries": 3,
"retry_no_answer_seconds": 3600,
"retry_completed_seconds": 86400,
"randomize_retry_time": true,
"double_dial_enabled": false,
"double_dial_gap_seconds": 90,
"max_calls_per_day": 150,
"min_seconds_between_calls": 45,
"max_in_flight": 2,
"budget_cents": 50000
}'
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.
{
"id": "b3f1c0d2-5a44-4f0e-9c11-7a2e8d3f0001",
"company_id": "fe493f11-0000-0000-0000-000000000001",
"name": "July renewals",
"description": "One call per renewal lead, up to three tries.",
"status": "draft",
"agent_id": "7e8a91c1-0000-4c11-9a00-000000000001",
"from_phone_number_id": "2d9f4b6a-0000-4a3c-8b21-000000000002",
"from_number": null,
"calling_window": {},
"stop_disposition_ids": [
"5c1d9a2e-0000-4b10-9f31-000000000010",
"5c1d9a2e-0000-4b10-9f31-000000000011"
],
"stop_on_no_answer": false,
"stop_on_voicemail": true,
"stop_on_unclassified": false,
"max_attempts": 3,
"max_infra_retries": 3,
"retry_no_answer_seconds": 3600,
"retry_completed_seconds": 86400,
"randomize_retry_time": true,
"double_dial_enabled": false,
"double_dial_gap_seconds": 90,
"max_calls_per_day": 150,
"min_seconds_between_calls": 45,
"max_in_flight": 2,
"daily_admitted_count": 0,
"daily_window_date": null,
"last_admitted_at": null,
"budget_cents": 50000,
"estimate_cents": null,
"spent_cents": 0,
"reserved_cents": 0,
"regulatory_basis": "existing_customer",
"last_tick_at": null,
"last_tick_result": null,
"last_error": null,
"starts_at": null,
"ends_at": null,
"started_at": null,
"completed_at": null,
"total_leads": 0,
"stats": {},
"created_at": "2026-07-28T09:14:22.123Z",
"updated_at": "2026-07-28T09:14:22.123Z",
"created_by": null,
"agent": {
"id": "7e8a91c1-0000-4c11-9a00-000000000001",
"name": "Renewals agent",
"max_call_duration_secs": 300
},
"from_phone_number": {
"id": "2d9f4b6a-0000-4a3c-8b21-000000000002",
"phone_number": "+972737000000",
"is_active": true,
"status": "active"
},
"stop_dispositions": [
{ "id": "5c1d9a2e-0000-4b10-9f31-000000000010", "label": "Do Not Call" },
{ "id": "5c1d9a2e-0000-4b10-9f31-000000000011", "label": "Not Interested" }
]
}
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | — | name missing, empty, or not a string. |
| 400 | — | Unknown or read-only field in the body. The message lists every writable field. |
| 400 | — | A numeric field is outside its allowed range, a boolean field is not a boolean, or budget_cents is not a positive integer. |
| 400 | — | regulatory_basis is not one of the four accepted values. |
| 400 | — | stop_disposition_ids is not an array of ID strings, or contains an ID that does not belong to your workspace. |
| 400 | — | retry_rules or calling_window is not an object. |
| 400 | — | Request body is not valid JSON. |
| 401 | INSUFFICIENT_SCOPE | API key lacks campaigns:manage. |
Authorizations
Your Yappr API key (e.g. ypr_live_...). Generate one in the dashboard under Settings → API Keys.
Body
"July reactivation"
Required before launch.
Required before launch.
Optional narrowing of the workspace calling hours. Cannot widen them.
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.
false
false
false
1 <= x <= 9993
0 <= x <= 203
30 <= x <= 6048003600
60 <= x <= 60480086400
false keeps a retry at the same time of day; true picks another hour inside the calling window.
false
false
10 <= x <= 360090
1 <= x <= 100000200
0 <= x <= 8640030
1 <= x <= 82
Hard spend cap. Null for no cap.
x >= 1Required before launch.
lawful_basis_confirmed, consent, existing_customer, non_marketing, registry_screened 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.
"July reactivation"
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.
draft, scheduled, running, paused, paused_insufficient_credit, paused_budget, paused_infra, paused_config, completed, stopped, archived Agent that runs the calls. Required before launch.
Full Agent object for agent_id.
Show child attributes
Show child attributes
Number the campaign calls from. Required before launch, and must still be active at launch time.
Full PhoneNumber object for from_phone_number_id.
Show child attributes
Show child attributes
Engine-owned. E.164 snapshot of the from-number, kept for audit even if the number is later released.
"+972551234567"
Optional per-outcome retry overrides. Free-form object — the explicit max_attempts / retry_*_seconds fields cover the common cases.
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.
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.
Full Disposition objects for stop_disposition_ids.
Show child attributes
Show child attributes
Retire a contact the first time a call goes unanswered.
false
Retire a contact the first time a call reaches an answering machine.
false
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.
false
Per-contact dial cap. One of the two independent stop conditions — the other is the stop-disposition set.
1 <= x <= 9993
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.
0 <= x <= 203
Delay before retrying a contact who did not answer.
30 <= x <= 6048003600
Delay before retrying a contact whose call completed but landed on a non-stop disposition.
60 <= x <= 60480086400
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.
false
Ring a second time shortly after an unanswered first ring.
false
Gap between the two rings when double_dial_enabled is true.
10 <= x <= 360090
Daily admission cap, counted against the workspace timezone day.
1 <= x <= 100000200
Minimum spacing between two calls handed to the queue by this campaign.
0 <= x <= 8640030
Calls this campaign may have live at once. Platform concurrency limits still apply on top — raising this does not buy the campaign extra capacity.
1 <= x <= 82
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.
x >= 1Your 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.
lawful_basis_confirmed, consent, existing_customer, non_marketing, registry_screened Do not admit contacts before this time.
Do not admit contacts after this time.
Engine-owned. Calls handed to the queue during the current day window.
Engine-owned. Day daily_admitted_count belongs to.
Engine-owned. When this campaign last handed a call to the queue.
Engine-owned. Projected campaign cost at the time of launch.
Engine-owned. Settled spend so far.
Engine-owned. Worst-case cost of in-flight calls, held against budget_cents until they settle.
Engine-owned. Last time the engine evaluated this campaign.
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.
Engine-owned. Last error the engine recorded for this campaign.
Engine-owned. First launch time.
Engine-owned. When the last contact reached a terminal state.
Engine-owned. Contacts currently enrolled.
Engine-owned counter roll-up. Prefer GET /campaigns/{id}/stats.
Null when the campaign was created via the API.