curl --request POST \
--url https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/campaigns \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'cloudchat-instance: <cloudchat-instance>' \
--data '
{
"campaign": {
"title": "September reactivation",
"message": "reactivation_september",
"audience": [
{
"type": "Label",
"id": 20010
}
],
"inbox_id": 60,
"email_campaign_sender_id": 4,
"subject": "We miss you",
"scheduled_at": "2026-10-01T14:00:00Z"
},
"confirm_recipients": 1187,
"template_params": {
"1": "{{contact.name}}",
"2": "15%"
},
"payload_params": {}
}
'import requests
url = "https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/campaigns"
payload = {
"campaign": {
"title": "September reactivation",
"message": "reactivation_september",
"audience": [
{
"type": "Label",
"id": 20010
}
],
"inbox_id": 60,
"email_campaign_sender_id": 4,
"subject": "We miss you",
"scheduled_at": "2026-10-01T14:00:00Z"
},
"confirm_recipients": 1187,
"template_params": {
"1": "{{contact.name}}",
"2": "15%"
},
"payload_params": {}
}
headers = {
"cloudchat-instance": "<cloudchat-instance>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'cloudchat-instance': '<cloudchat-instance>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
campaign: {
title: 'September reactivation',
message: 'reactivation_september',
audience: [{type: 'Label', id: 20010}],
inbox_id: 60,
email_campaign_sender_id: 4,
subject: 'We miss you',
scheduled_at: '2026-10-01T14:00:00Z'
},
confirm_recipients: 1187,
template_params: {'1': '{{contact.name}}', '2': '15%'},
payload_params: {}
})
};
fetch('https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/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.cloudhumans.com/cloudchat/v1/accounts/{accountId}/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' => [
'title' => 'September reactivation',
'message' => 'reactivation_september',
'audience' => [
[
'type' => 'Label',
'id' => 20010
]
],
'inbox_id' => 60,
'email_campaign_sender_id' => 4,
'subject' => 'We miss you',
'scheduled_at' => '2026-10-01T14:00:00Z'
],
'confirm_recipients' => 1187,
'template_params' => [
'1' => '{{contact.name}}',
'2' => '15%'
],
'payload_params' => [
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"cloudchat-instance: <cloudchat-instance>"
],
]);
$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.cloudhumans.com/cloudchat/v1/accounts/{accountId}/campaigns"
payload := strings.NewReader("{\n \"campaign\": {\n \"title\": \"September reactivation\",\n \"message\": \"reactivation_september\",\n \"audience\": [\n {\n \"type\": \"Label\",\n \"id\": 20010\n }\n ],\n \"inbox_id\": 60,\n \"email_campaign_sender_id\": 4,\n \"subject\": \"We miss you\",\n \"scheduled_at\": \"2026-10-01T14:00:00Z\"\n },\n \"confirm_recipients\": 1187,\n \"template_params\": {\n \"1\": \"{{contact.name}}\",\n \"2\": \"15%\"\n },\n \"payload_params\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("cloudchat-instance", "<cloudchat-instance>")
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.cloudhumans.com/cloudchat/v1/accounts/{accountId}/campaigns")
.header("cloudchat-instance", "<cloudchat-instance>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"campaign\": {\n \"title\": \"September reactivation\",\n \"message\": \"reactivation_september\",\n \"audience\": [\n {\n \"type\": \"Label\",\n \"id\": 20010\n }\n ],\n \"inbox_id\": 60,\n \"email_campaign_sender_id\": 4,\n \"subject\": \"We miss you\",\n \"scheduled_at\": \"2026-10-01T14:00:00Z\"\n },\n \"confirm_recipients\": 1187,\n \"template_params\": {\n \"1\": \"{{contact.name}}\",\n \"2\": \"15%\"\n },\n \"payload_params\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/campaigns")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["cloudchat-instance"] = '<cloudchat-instance>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"campaign\": {\n \"title\": \"September reactivation\",\n \"message\": \"reactivation_september\",\n \"audience\": [\n {\n \"type\": \"Label\",\n \"id\": 20010\n }\n ],\n \"inbox_id\": 60,\n \"email_campaign_sender_id\": 4,\n \"subject\": \"We miss you\",\n \"scheduled_at\": \"2026-10-01T14:00:00Z\"\n },\n \"confirm_recipients\": 1187,\n \"template_params\": {\n \"1\": \"{{contact.name}}\",\n \"2\": \"15%\"\n },\n \"payload_params\": {}\n}"
response = http.request(request)
puts response.read_body{
"id": 176,
"title": "September reactivation",
"message": "reactivation_september",
"campaign_status": "completed",
"campaign_type": "one_off",
"created_at": "2026-09-19T17:34:12.881Z",
"updated_at": "2026-09-19T17:34:15.002Z",
"reachable_count": 1187,
"subject": "<string>",
"inbox_id": 60,
"email_campaign_sender_id": 123,
"sender_id": 895,
"conversation_label_id": 123,
"team_id": 123,
"recipients_count": 1240,
"scheduled_at": "2026-09-19T17:34:12.881Z",
"started_at": "2023-11-07T05:31:56Z",
"completed_at": "2026-09-19T17:34:15.002Z"
}{
"error": {
"code": "bad_request",
"message": "The request is malformed."
}
}{
"error": {
"code": "unauthorized",
"message": "Authentication is required. Send a valid Bearer token in the Authorization header."
}
}{
"error": {
"code": "forbidden",
"message": "You do not have permission to perform this action."
}
}{
"error": {
"code": "not_found",
"message": "Resource could not be found."
}
}{
"error": {
"code": "conflict",
"message": "The request payload is invalid.",
"details": [
{
"field": "confirm_recipients",
"code": "mismatch",
"message": "You confirmed 800 recipients but this audience currently reaches 1187. Preview the recipients again and resend with the current number."
}
]
}
}{
"error": {
"code": "validation_failed",
"message": "The request payload is invalid.",
"details": [
{
"field": "message",
"code": "template_not_found",
"message": "Template \"reactivation\" is not approved for this inbox. Approved templates: reactivation_september, welcome_back."
}
]
}
}{
"error": {
"code": "rate_limited",
"message": "Too many requests. Try again in a moment."
}
}{
"error": {
"code": "internal_error",
"message": "An unexpected error occurred. Please try again later."
}
}Create a campaign
Creating a campaign sends it. There is no separate step that starts the send, and no way to call it back: whatever you create here goes out. confirm_recipients has to equal the reachable the preview returned for the same audience in the same channel; if it does not, the answer is 409 with both numbers and nothing is created.
Do not repeat this call after a timeout. A repeat creates a second campaign and sends a second time, and every WhatsApp message is billed. Reading the campaign back only helps if you already hold its id: there is no listing and no idempotency key, so a lost response cannot be resolved through this API — open the campaign dashboard in Cloud Chat to see whether it was created before you send again.
The channel follows from what you send. A campaign carrying email_campaign_sender_id is an e-mail campaign; otherwise the channel comes from the channel_type of the inbox behind inbox_id: Channel::Whatsapp sends a WhatsApp template, Channel::WebWidget sends a widget message. Check channel_type first, because nothing cross-checks your intent: a website inbox chosen while you meant to send WhatsApp delivers the template name to your contacts as the literal text of the message.
This response is not a delivery confirmation. Sending happens after it returns and can still fail; what actually reached your contacts is in the Cloud Chat campaign dashboard. Every WhatsApp message is billed and reaches a real person, so do not send one merely to rehearse. To check an audience without sending, use the preview endpoint.
curl --request POST \
--url https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/campaigns \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'cloudchat-instance: <cloudchat-instance>' \
--data '
{
"campaign": {
"title": "September reactivation",
"message": "reactivation_september",
"audience": [
{
"type": "Label",
"id": 20010
}
],
"inbox_id": 60,
"email_campaign_sender_id": 4,
"subject": "We miss you",
"scheduled_at": "2026-10-01T14:00:00Z"
},
"confirm_recipients": 1187,
"template_params": {
"1": "{{contact.name}}",
"2": "15%"
},
"payload_params": {}
}
'import requests
url = "https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/campaigns"
payload = {
"campaign": {
"title": "September reactivation",
"message": "reactivation_september",
"audience": [
{
"type": "Label",
"id": 20010
}
],
"inbox_id": 60,
"email_campaign_sender_id": 4,
"subject": "We miss you",
"scheduled_at": "2026-10-01T14:00:00Z"
},
"confirm_recipients": 1187,
"template_params": {
"1": "{{contact.name}}",
"2": "15%"
},
"payload_params": {}
}
headers = {
"cloudchat-instance": "<cloudchat-instance>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'cloudchat-instance': '<cloudchat-instance>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
campaign: {
title: 'September reactivation',
message: 'reactivation_september',
audience: [{type: 'Label', id: 20010}],
inbox_id: 60,
email_campaign_sender_id: 4,
subject: 'We miss you',
scheduled_at: '2026-10-01T14:00:00Z'
},
confirm_recipients: 1187,
template_params: {'1': '{{contact.name}}', '2': '15%'},
payload_params: {}
})
};
fetch('https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/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.cloudhumans.com/cloudchat/v1/accounts/{accountId}/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' => [
'title' => 'September reactivation',
'message' => 'reactivation_september',
'audience' => [
[
'type' => 'Label',
'id' => 20010
]
],
'inbox_id' => 60,
'email_campaign_sender_id' => 4,
'subject' => 'We miss you',
'scheduled_at' => '2026-10-01T14:00:00Z'
],
'confirm_recipients' => 1187,
'template_params' => [
'1' => '{{contact.name}}',
'2' => '15%'
],
'payload_params' => [
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"cloudchat-instance: <cloudchat-instance>"
],
]);
$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.cloudhumans.com/cloudchat/v1/accounts/{accountId}/campaigns"
payload := strings.NewReader("{\n \"campaign\": {\n \"title\": \"September reactivation\",\n \"message\": \"reactivation_september\",\n \"audience\": [\n {\n \"type\": \"Label\",\n \"id\": 20010\n }\n ],\n \"inbox_id\": 60,\n \"email_campaign_sender_id\": 4,\n \"subject\": \"We miss you\",\n \"scheduled_at\": \"2026-10-01T14:00:00Z\"\n },\n \"confirm_recipients\": 1187,\n \"template_params\": {\n \"1\": \"{{contact.name}}\",\n \"2\": \"15%\"\n },\n \"payload_params\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("cloudchat-instance", "<cloudchat-instance>")
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.cloudhumans.com/cloudchat/v1/accounts/{accountId}/campaigns")
.header("cloudchat-instance", "<cloudchat-instance>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"campaign\": {\n \"title\": \"September reactivation\",\n \"message\": \"reactivation_september\",\n \"audience\": [\n {\n \"type\": \"Label\",\n \"id\": 20010\n }\n ],\n \"inbox_id\": 60,\n \"email_campaign_sender_id\": 4,\n \"subject\": \"We miss you\",\n \"scheduled_at\": \"2026-10-01T14:00:00Z\"\n },\n \"confirm_recipients\": 1187,\n \"template_params\": {\n \"1\": \"{{contact.name}}\",\n \"2\": \"15%\"\n },\n \"payload_params\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cloudhumans.com/cloudchat/v1/accounts/{accountId}/campaigns")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["cloudchat-instance"] = '<cloudchat-instance>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"campaign\": {\n \"title\": \"September reactivation\",\n \"message\": \"reactivation_september\",\n \"audience\": [\n {\n \"type\": \"Label\",\n \"id\": 20010\n }\n ],\n \"inbox_id\": 60,\n \"email_campaign_sender_id\": 4,\n \"subject\": \"We miss you\",\n \"scheduled_at\": \"2026-10-01T14:00:00Z\"\n },\n \"confirm_recipients\": 1187,\n \"template_params\": {\n \"1\": \"{{contact.name}}\",\n \"2\": \"15%\"\n },\n \"payload_params\": {}\n}"
response = http.request(request)
puts response.read_body{
"id": 176,
"title": "September reactivation",
"message": "reactivation_september",
"campaign_status": "completed",
"campaign_type": "one_off",
"created_at": "2026-09-19T17:34:12.881Z",
"updated_at": "2026-09-19T17:34:15.002Z",
"reachable_count": 1187,
"subject": "<string>",
"inbox_id": 60,
"email_campaign_sender_id": 123,
"sender_id": 895,
"conversation_label_id": 123,
"team_id": 123,
"recipients_count": 1240,
"scheduled_at": "2026-09-19T17:34:12.881Z",
"started_at": "2023-11-07T05:31:56Z",
"completed_at": "2026-09-19T17:34:15.002Z"
}{
"error": {
"code": "bad_request",
"message": "The request is malformed."
}
}{
"error": {
"code": "unauthorized",
"message": "Authentication is required. Send a valid Bearer token in the Authorization header."
}
}{
"error": {
"code": "forbidden",
"message": "You do not have permission to perform this action."
}
}{
"error": {
"code": "not_found",
"message": "Resource could not be found."
}
}{
"error": {
"code": "conflict",
"message": "The request payload is invalid.",
"details": [
{
"field": "confirm_recipients",
"code": "mismatch",
"message": "You confirmed 800 recipients but this audience currently reaches 1187. Preview the recipients again and resend with the current number."
}
]
}
}{
"error": {
"code": "validation_failed",
"message": "The request payload is invalid.",
"details": [
{
"field": "message",
"code": "template_not_found",
"message": "Template \"reactivation\" is not approved for this inbox. Approved templates: reactivation_september, welcome_back."
}
]
}
}{
"error": {
"code": "rate_limited",
"message": "Too many requests. Try again in a moment."
}
}{
"error": {
"code": "internal_error",
"message": "An unexpected error occurred. Please try again later."
}
}Authorizations
The id_token from POST /auth/v1/signin, sent as Authorization: Bearer <id_token>. Not the access_token — that one does not carry the identity Cloud Chat authorizes on.
Headers
Your Cloud Chat instance ID — an integer, fixed for your company, told at onboarding. The API overview explains how instances work, how to find yours, and the errors a wrong or missing value produces.
1
Path Parameters
Your Cloud Chat account. It has to be an account your token grants membership on, and it has to live on the instance in the cloudchat-instance header — the two travel together. Account numbers are only unique within an instance, so the same number is a different company on another instance. Usually a mismatched pair fails closed with a 401, because your user does not exist on the other instance — but if your identity happens to exist on both, the call succeeds against the other company's data, silently. Read it and you are looking at the wrong help center; write it and you have stored into the wrong account. Send the two values that were given to you together, and never try a number to see what answers.
1
Body
A campaign to create and send, together with the reach you are confirming.
The campaign itself. Which fields apply depends on the channel, and the channel is never passed directly: it follows from email_campaign_sender_id, or from the type of the inbox.
Show child attributes
Show child attributes
The reachable count you are confirming. It has to equal the reachable the preview returned for this exact audience and channel; any other value answers 409 with both numbers and creates nothing. Leaving it out is a 409 too, not a default.
1187
Values for a WhatsApp template placeholders, keyed by the exact token between the braces: "1", "2" and so on for a positional template, the variable name for a named one. Send exactly as many keys as the template parameters count. A value wrapped in double braces, such as {{contact.name}}, is resolved per recipient at send time; anything else is sent literally to everyone.
Show child attributes
Show child attributes
{ "1": "{{contact.name}}", "2": "15%" }
Values for placeholders inside the template buttons, keyed the same way. Only templates whose buttons carry a placeholder need this.
Show child attributes
Show child attributes
Response
Created, and already on its way. This is the only response that carries reachable_count.
A one-off campaign as stored. reachable_count is present only in the create response.
The display id, sequential within the account. Keep it — no endpoint lists campaigns, so this is the only way back to it.
176
Internal name of the campaign.
"September reactivation"
The template name on a WhatsApp campaign, the message body on e-mail and widget.
"reactivation_september"
Where the campaign is in its lifecycle — not a delivery report. completed means a worker took it over, not that anything arrived: on WhatsApp it is set before the audience is even resolved. failed means a precondition stopped the dispatch and it will not be retried. What actually reached your contacts is in the Cloud Chat campaign dashboard.
The values in use are active, completed, running, draft, partially_completed and failed. The set is open and grows as new states are added, so read an unfamiliar value as a state you do not know yet rather than as an error.
"completed"
Campaigns created through the API are always one_off. A campaign built in Cloud Chat can be ongoing and fire on a trigger instead.
ongoing, one_off "one_off"
When the campaign was created.
"2026-09-19T17:34:12.881Z"
When it last changed.
"2026-09-19T17:34:15.002Z"
The reach the campaign was confirmed against. Returned only when the campaign is created: reach is computed, never stored, so reading a campaign back omits the field entirely.
1187
Subject line of an e-mail campaign; null on the other channels.
The inbox that sends. On an e-mail campaign it follows from the sender.
60
The verified sender of an e-mail campaign; null on WhatsApp and widget.
The user the campaign is attributed to. Through the API this is always the identity of the token that created it — authorship cannot be set in the payload.
895
Label applied to the conversations the campaign opens, when one was configured.
Team the resulting conversations are assigned to, when one was configured.
The raw audience size recorded on the campaign, before the reachability check. It is not what was delivered, and it is not what confirm_recipients is compared against.
1240
When the campaign is due out. A campaign created without one carries the moment it was created.
"2026-09-19T17:34:12.881Z"
When dispatch began. Always null on a WhatsApp campaign — only the e-mail path fills it — so its absence tells you nothing about whether the campaign ran.
When the campaign reached a terminal status; null while it is still open.
"2026-09-19T17:34:15.002Z"