Campaigns
Create campaign
Create an outbound batch campaign with a contact list, retry logic, optional scheduling, and optional business-hours gating. Campaigns start in draft status until you resume them.
POST
/
campaigns
Create campaign
curl --request POST \
--url https://api.verbeo.ai/v1/campaigns \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"campaign_name": "<string>",
"agent_id": "<string>",
"phone_number": "<string>",
"contacts": [
{
"phone_number": "<string>",
"contact_name": "<string>",
"custom_data": {}
}
],
"max_retries": 123,
"retry_intervals": [
123
],
"scheduled_at": "<string>",
"business_hours": {
"tz": "<string>",
"days": [
123
],
"windows": [
{}
]
}
}
'import requests
url = "https://api.verbeo.ai/v1/campaigns"
payload = {
"campaign_name": "<string>",
"agent_id": "<string>",
"phone_number": "<string>",
"contacts": [
{
"phone_number": "<string>",
"contact_name": "<string>",
"custom_data": {}
}
],
"max_retries": 123,
"retry_intervals": [123],
"scheduled_at": "<string>",
"business_hours": {
"tz": "<string>",
"days": [123],
"windows": [{}]
}
}
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({
campaign_name: '<string>',
agent_id: '<string>',
phone_number: '<string>',
contacts: [{phone_number: '<string>', contact_name: '<string>', custom_data: {}}],
max_retries: 123,
retry_intervals: [123],
scheduled_at: '<string>',
business_hours: {tz: '<string>', days: [123], windows: [{}]}
})
};
fetch('https://api.verbeo.ai/v1/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.verbeo.ai/v1/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([
'campaign_name' => '<string>',
'agent_id' => '<string>',
'phone_number' => '<string>',
'contacts' => [
[
'phone_number' => '<string>',
'contact_name' => '<string>',
'custom_data' => [
]
]
],
'max_retries' => 123,
'retry_intervals' => [
123
],
'scheduled_at' => '<string>',
'business_hours' => [
'tz' => '<string>',
'days' => [
123
],
'windows' => [
[
]
]
]
]),
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.verbeo.ai/v1/campaigns"
payload := strings.NewReader("{\n \"campaign_name\": \"<string>\",\n \"agent_id\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"contacts\": [\n {\n \"phone_number\": \"<string>\",\n \"contact_name\": \"<string>\",\n \"custom_data\": {}\n }\n ],\n \"max_retries\": 123,\n \"retry_intervals\": [\n 123\n ],\n \"scheduled_at\": \"<string>\",\n \"business_hours\": {\n \"tz\": \"<string>\",\n \"days\": [\n 123\n ],\n \"windows\": [\n {}\n ]\n }\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.verbeo.ai/v1/campaigns")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"campaign_name\": \"<string>\",\n \"agent_id\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"contacts\": [\n {\n \"phone_number\": \"<string>\",\n \"contact_name\": \"<string>\",\n \"custom_data\": {}\n }\n ],\n \"max_retries\": 123,\n \"retry_intervals\": [\n 123\n ],\n \"scheduled_at\": \"<string>\",\n \"business_hours\": {\n \"tz\": \"<string>\",\n \"days\": [\n 123\n ],\n \"windows\": [\n {}\n ]\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.verbeo.ai/v1/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 \"campaign_name\": \"<string>\",\n \"agent_id\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"contacts\": [\n {\n \"phone_number\": \"<string>\",\n \"contact_name\": \"<string>\",\n \"custom_data\": {}\n }\n ],\n \"max_retries\": 123,\n \"retry_intervals\": [\n 123\n ],\n \"scheduled_at\": \"<string>\",\n \"business_hours\": {\n \"tz\": \"<string>\",\n \"days\": [\n 123\n ],\n \"windows\": [\n {}\n ]\n }\n}"
response = http.request(request)
puts response.read_bodyA campaign places outbound AI voice calls to a list of contacts. Campaigns are created in
Each contact gets a personalized call with their own values substituted.
draft status and do not start dialing until you call Resume, unless you set scheduled_at, in which case they auto-start at that time.
Request body
string
required
Display name shown in the dashboard.
string
required
UUID of the agent that will handle calls in this campaign. Must belong to your workspace.
string
required
Outbound phone number to dial from, in E.164 format (e.g.
"+15551234567"). Must be registered to your workspace — the number visible on Settings → Phone Numbers in the dashboard.object[]
required
Recipients to dial. Must contain at least one entry. There is no upper limit.
number
default:"0"
Additional attempts per contact after the initial call.
2 means up to 3 total attempts. Retries fire on no-answer, voicemail, busy, or transient dial failure. A connected call that hangs up is considered successful.number[]
Wait times in minutes between attempts. The Nth retry waits
retry_intervals[N-1] minutes after the previous attempt. If shorter than max_retries, the last value is reused.string
Future ISO 8601 UTC timestamp at which the campaign should auto-start. Sets the initial status to
scheduled instead of draft.object | null
Time-of-day / weekday gate for outbound dialing. Omit or send
null for 24/7 dispatch.Show business_hours properties
Show business_hours properties
string
required
IANA timezone (e.g.
"America/New_York").number[]
required
Allowed ISO weekdays —
1 = Monday … 7 = Sunday. Pick at least one.object[]
required
One or more daily windows. Each is
{ start, end } in minutes since local midnight. Constraints: 0 ≤ start < end ≤ 1440. Cross-midnight windows aren’t supported — split them across two adjacent days.Outside
business_hours, the campaign moves to dormant and resumes automatically when the next allowed window opens.Request
curl -X POST "https://api.verbeo.ai/v1/campaigns" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"campaign_name": "Q2 Sales Outreach",
"agent_id": "550e8400-e29b-41d4-a716-446655440000",
"phone_number": "+15551234567",
"contacts": [
{
"phone_number": "+12125550001",
"contact_name": "Alice Johnson",
"custom_data": { "company": "Acme Corp", "tier": "gold" }
},
{ "phone_number": "+12125550002", "contact_name": "Bob Smith" }
],
"max_retries": 2,
"retry_intervals": [5, 30],
"business_hours": {
"tz": "America/New_York",
"days": [1, 2, 3, 4, 5],
"windows": [{ "start": 540, "end": 1020 }]
}
}'
const res = await fetch("https://api.verbeo.ai/v1/campaigns", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VERBEO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
campaign_name: "Q2 Sales Outreach",
agent_id: "550e8400-e29b-41d4-a716-446655440000",
phone_number: "+15551234567",
contacts: [
{ phone_number: "+12125550001", contact_name: "Alice Johnson" },
],
max_retries: 2,
retry_intervals: [5, 30],
}),
});
const { data } = await res.json();
const campaignId = data.campaign_id;
import os, requests
res = requests.post(
"https://api.verbeo.ai/v1/campaigns",
headers={
"Authorization": f"Bearer {os.environ['VERBEO_API_KEY']}",
"Content-Type": "application/json",
},
json={
"campaign_name": "Q2 Sales Outreach",
"agent_id": "550e8400-e29b-41d4-a716-446655440000",
"phone_number": "+15551234567",
"contacts": [
{"phone_number": "+12125550001", "contact_name": "Alice Johnson"},
],
"max_retries": 2,
"retry_intervals": [5, 30],
},
)
campaign_id = res.json()["data"]["campaign_id"]
Response
201 Created. See Responses for the envelope.
{
"success": true,
"data": {
"campaign_id": "0175091d-2c3b-4aef-8c45-dbbe24f7b3ff",
"campaign_name": "Q2 Sales Outreach",
"status": "draft",
"total_contacts": 2,
"business_hours": {
"tz": "America/New_York",
"days": [1, 2, 3, 4, 5],
"windows": [{ "start": 540, "end": 1020 }]
},
"scheduled_at": null,
"created_at": "2026-05-04T15:00:00.000Z"
}
}
Using custom data in your agent
Reference any field fromcustom_data (and contact_name) in your agent’s system prompt:
You are calling {{contact_name}} from {{company}}.
They are a {{tier}} customer. Be prepared to discuss their renewal options.
Errors
This endpoint can returnVALIDATION_FAILED, AGENT_NOT_FOUND, PHONE_NOT_FOUND, CREATE_FAILED, and CONTACTS_INSERT_FAILED — see Campaigns overview for status codes and meanings, and Errors for the standard envelope.
After creating a campaign in
draft status, call Resume to start dialing.Was this page helpful?
⌘I
Create campaign
curl --request POST \
--url https://api.verbeo.ai/v1/campaigns \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"campaign_name": "<string>",
"agent_id": "<string>",
"phone_number": "<string>",
"contacts": [
{
"phone_number": "<string>",
"contact_name": "<string>",
"custom_data": {}
}
],
"max_retries": 123,
"retry_intervals": [
123
],
"scheduled_at": "<string>",
"business_hours": {
"tz": "<string>",
"days": [
123
],
"windows": [
{}
]
}
}
'import requests
url = "https://api.verbeo.ai/v1/campaigns"
payload = {
"campaign_name": "<string>",
"agent_id": "<string>",
"phone_number": "<string>",
"contacts": [
{
"phone_number": "<string>",
"contact_name": "<string>",
"custom_data": {}
}
],
"max_retries": 123,
"retry_intervals": [123],
"scheduled_at": "<string>",
"business_hours": {
"tz": "<string>",
"days": [123],
"windows": [{}]
}
}
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({
campaign_name: '<string>',
agent_id: '<string>',
phone_number: '<string>',
contacts: [{phone_number: '<string>', contact_name: '<string>', custom_data: {}}],
max_retries: 123,
retry_intervals: [123],
scheduled_at: '<string>',
business_hours: {tz: '<string>', days: [123], windows: [{}]}
})
};
fetch('https://api.verbeo.ai/v1/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.verbeo.ai/v1/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([
'campaign_name' => '<string>',
'agent_id' => '<string>',
'phone_number' => '<string>',
'contacts' => [
[
'phone_number' => '<string>',
'contact_name' => '<string>',
'custom_data' => [
]
]
],
'max_retries' => 123,
'retry_intervals' => [
123
],
'scheduled_at' => '<string>',
'business_hours' => [
'tz' => '<string>',
'days' => [
123
],
'windows' => [
[
]
]
]
]),
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.verbeo.ai/v1/campaigns"
payload := strings.NewReader("{\n \"campaign_name\": \"<string>\",\n \"agent_id\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"contacts\": [\n {\n \"phone_number\": \"<string>\",\n \"contact_name\": \"<string>\",\n \"custom_data\": {}\n }\n ],\n \"max_retries\": 123,\n \"retry_intervals\": [\n 123\n ],\n \"scheduled_at\": \"<string>\",\n \"business_hours\": {\n \"tz\": \"<string>\",\n \"days\": [\n 123\n ],\n \"windows\": [\n {}\n ]\n }\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.verbeo.ai/v1/campaigns")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"campaign_name\": \"<string>\",\n \"agent_id\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"contacts\": [\n {\n \"phone_number\": \"<string>\",\n \"contact_name\": \"<string>\",\n \"custom_data\": {}\n }\n ],\n \"max_retries\": 123,\n \"retry_intervals\": [\n 123\n ],\n \"scheduled_at\": \"<string>\",\n \"business_hours\": {\n \"tz\": \"<string>\",\n \"days\": [\n 123\n ],\n \"windows\": [\n {}\n ]\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.verbeo.ai/v1/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 \"campaign_name\": \"<string>\",\n \"agent_id\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"contacts\": [\n {\n \"phone_number\": \"<string>\",\n \"contact_name\": \"<string>\",\n \"custom_data\": {}\n }\n ],\n \"max_retries\": 123,\n \"retry_intervals\": [\n 123\n ],\n \"scheduled_at\": \"<string>\",\n \"business_hours\": {\n \"tz\": \"<string>\",\n \"days\": [\n 123\n ],\n \"windows\": [\n {}\n ]\n }\n}"
response = http.request(request)
puts response.read_body