. Create an API key under API keys.API reference · Messaging
Broadcasts
Campaigns to many recipients: explicit lists or CRM audiences (segments, groups, tags), personalization, consent checks, the daily send window and progress counters.
List the campaigns of a device
/v1/sessions/{sessionId}/blastsList the campaigns of a session, newest first.
Path parameters
sessionIdstringrequired
Responses
- 200Campaigns
Response fields
campaignsarray of objectrequiredcampaigns[].idstringrequiredcampaigns[].session_idstringrequiredcampaigns[].namestringrequiredcampaigns[].statusstringrequiredcampaigns[].bodystring | nullrequiredcampaigns[].message_typestringrequiredcampaigns[].total_recipientsintegerrequiredcampaigns[].sent_countintegerrequiredcampaigns[].failed_countintegerrequiredcampaigns[].created_atintegerrequiredcampaigns[].started_atinteger | nullrequiredcampaigns[].completed_atinteger | nullrequiredcampaigns[].consent_attested_atinteger | nullrequiredWhen the sender confirmed the recipients agreed to be messaged (consent_attested), if ever
curl "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.get(
"https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"campaigns": [
{
"id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
"session_id": "YOUR_DEVICE_ID",
"name": "Example",
"status": "draft",
"body": "Hi Sam, your order has shipped.",
"message_type": "Hi Sam, your order has shipped.",
"total_recipients": 12,
"sent_count": 12,
"failed_count": 12,
"created_at": 1767225600000,
"started_at": 1767225600000,
"completed_at": 1767225600000,
"consent_attested_at": 1767225600000
}
]
}Create a campaign
/v1/sessions/{sessionId}/blastsCreate a blast campaign (status draft). The audience is the explicit recipients plus the contacts matched by segment_id, group_id and/or tag (CRM filters combine with AND), deduplicated, max 50 000. Nothing is sent until /start. The server applies anti-ban pacing and per-country warm-up; recipients who opted out are skipped automatically just before sending, and bulk sends are only released inside the tenant's send window (see send_window in /v1/settings/send_window).
Path parameters
sessionIdstringrequired
Request body
namestringrequiredCampaign name (internal)message_typestringbodystringMessage text. Personalization tokens:{name},{first_name},{phone},{var1}…{var5},{attr.<key>}; add a fallback with double braces:{{first_name|there}},{{attr.plan|your plan}}. A token without a value and without a fallback becomes empty.media_r2_keystringMedia key from /v1/media/upload-url (for image/video/document/audio)captionstringfooterstringbuttonsanyOptional button configurationdelay_configobjectPause between recipients. Default 3 000–10 000 ms with 1 000–3 000 ms of simulated typing.delay_config.min_msintegerrequireddelay_config.max_msintegerrequireddelay_config.typing_min_msintegerrequireddelay_config.typing_max_msintegerrequiredsimulate_typingbooleanShow "typing…" before each message (default true)recipientsarray of objectExplicit recipients. Combined (deduplicated) with the CRM audience below.recipients[].chat_idstringrequiredPhone number (+<country code><number>; local numbers use the business country) or JIDrecipients[].namestringrecipients[].varsarray of stringValues for{var1}…{var5}recipients[].attrsobjectPer-recipient values for{attr.<key>}segment_idstringAdd every contact of this saved segmentgroup_idstringAdd every contact of this contact grouptagstringAdd every contact carrying this tagconsent_attestedbooleanConfirms the recipients agreed to receive these messages. Required (here or when starting) if the tenant turned onrequire_attestation.
Responses
- 201Draft created
Response fields
idstringrequiredstatusstringrequiredtotal_recipientsintegerrequired
- 400Validation error
- 404Segment or group not found
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "October promo",
"body": "Hi {{first_name|there}}, 20% off everything this week!",
"recipients": [
{
"chat_id": "+447700900123",
"name": "Sam Carter",
"attrs": {
"plan": "Pro"
}
},
{
"chat_id": "+12025550123",
"name": "Alex Kim"
}
]
}'const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "October promo",
"body": "Hi {{first_name|there}}, 20% off everything this week!",
"recipients": [
{
"chat_id": "+447700900123",
"name": "Sam Carter",
"attrs": {
"plan": "Pro"
}
},
{
"chat_id": "+12025550123",
"name": "Alex Kim"
}
]
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.post(
"https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"name": "October promo",
"body": "Hi {{first_name|there}}, 20% off everything this week!",
"recipients": [
{
"chat_id": "+447700900123",
"name": "Sam Carter",
"attrs": {
"plan": "Pro",
},
},
{
"chat_id": "+12025550123",
"name": "Alex Kim",
},
],
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'name' => 'October promo',
'body' => 'Hi {{first_name|there}}, 20% off everything this week!',
'recipients' => [
[
'chat_id' => '+447700900123',
'name' => 'Sam Carter',
'attrs' => [
'plan' => 'Pro',
],
],
[
'chat_id' => '+12025550123',
'name' => 'Alex Kim',
],
],
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
"status": "draft",
"total_recipients": 12
}Get a campaign and its progress
/v1/sessions/{sessionId}/blasts/{campaignId}Campaign detail with per-recipient status and progress counters. skipped_count counts recipients removed because they opted out.
Path parameters
sessionIdstringrequiredcampaignIdstringrequired
Responses
- 200Campaign
Response fields
campaignobjectrequiredcampaign.idstringrequiredcampaign.session_idstringrequiredcampaign.namestringrequiredcampaign.statusstringrequiredcampaign.bodystring | nullrequiredcampaign.message_typestringrequiredcampaign.total_recipientsintegerrequiredcampaign.sent_countintegerrequiredcampaign.failed_countintegerrequiredcampaign.created_atintegerrequiredcampaign.started_atinteger | nullrequiredcampaign.completed_atinteger | nullrequiredcampaign.consent_attested_atinteger | nullrequiredWhen the sender confirmed the recipients agreed to be messaged (consent_attested), if everrecipientsarray of objectrequiredrecipients[].idstringrequiredrecipients[].chat_idstringrequiredrecipients[].namestring | nullrequiredrecipients[].statusstringrequiredpending,processing,sent,failed,skippedorcancelledrecipients[].errorstring | nullrequiredpending_countintegerrequiredsent_countintegerrequiredfailed_countintegerrequiredskipped_countintegerrequired
- 404Campaign not found
curl "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.get(
"https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"campaign": {
"id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
"session_id": "YOUR_DEVICE_ID",
"name": "Example",
"status": "draft",
"body": "Hi Sam, your order has shipped.",
"message_type": "Hi Sam, your order has shipped.",
"total_recipients": 12,
"sent_count": 12,
"failed_count": 12,
"created_at": 1767225600000,
"started_at": 1767225600000,
"completed_at": 1767225600000,
"consent_attested_at": 1767225600000
},
"recipients": [
{
"id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
"chat_id": "+447700900123",
"name": "Example",
"status": "active",
"error": "string"
}
],
"pending_count": 12,
"sent_count": 12,
"failed_count": 12,
"skipped_count": 12
}Delete a campaign
/v1/sessions/{sessionId}/blasts/{campaignId}Deletes the campaign and all its recipient rows, whatever its status. Messages that were already sent stay in the message history.
Path parameters
sessionIdstringrequiredSession (device) idcampaignIdstringrequiredCampaign id
Responses
- 200Deleted
Response fields
okbooleanrequired
- 401Missing or invalid credentials
- 403The API key is restricted to other sessions, or the operation is not available to CS-agent logins
- 404Campaign not found (
NOT_FOUND); unknown session, or a session of another tenant
curl -X DELETE "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.delete(
"https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('DELETE', 'https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"ok": true
}Start or resume a campaign
/v1/sessions/{sessionId}/blasts/{campaignId}/startStart or resume a campaign. If the tenant turned on require_attestation (consent config) and the campaign was not attested at creation, the body must contain {"consent_attested": true}, otherwise the call fails with 400 CONSENT_ATTESTATION_REQUIRED. Opted-out recipients are removed (status skipped) before the first message.
Path parameters
sessionIdstringrequiredcampaignIdstringrequired
Request body
consent_attestedboolean
Responses
- 200Started
Response fields
okbooleanrequiredstatusstringrequired
- 400
CONSENT_ATTESTATION_REQUIRED - 404Campaign not found
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID/start" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"consent_attested": true
}'const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID/start', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"consent_attested": true
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.post(
"https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID/start",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"consent_attested": True,
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID/start', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'consent_attested' => true,
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"ok": true,
"status": "running"
}Pause a campaign
/v1/sessions/{sessionId}/blasts/{campaignId}/pausePause a running campaign (resume with /start).
Path parameters
sessionIdstringrequiredcampaignIdstringrequired
Responses
- 200Updated
Response fields
okbooleanrequiredstatusstringrequired
- 404Campaign not found
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID/pause" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID/pause', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.post(
"https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID/pause",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID/pause', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"ok": true,
"status": "active"
}Cancel a campaign
/v1/sessions/{sessionId}/blasts/{campaignId}/cancelCancel a campaign; recipients that were still pending are cancelled.
Path parameters
sessionIdstringrequiredcampaignIdstringrequired
Responses
- 200Updated
Response fields
okbooleanrequiredstatusstringrequired
- 404Campaign not found
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID/cancel" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID/cancel', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.post(
"https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID/cancel",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/blasts/CAMPAIGN_ID/cancel', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"ok": true,
"status": "active"
}List blast campaigns of all devices
/v1/blastsEvery blast campaign of the tenant across devices, newest first, without pagination. Campaigns are created per device with POST /v1/sessions/{sessionId}/blasts.
Responses
- 200Campaigns
Response fields
campaignsarray of objectrequiredcampaigns[].idstringrequiredcampaigns[].session_idstringrequiredcampaigns[].api_key_idstringrequiredOwning tenantcampaigns[].namestringrequiredcampaigns[].statusstringrequireddraft,running,paused,completed,failedorcancelledcampaigns[].message_typestringrequiredcampaigns[].bodystring | nullrequiredcampaigns[].total_recipientsinteger | nullrequiredcampaigns[].sent_countinteger | nullrequiredcampaigns[].failed_countinteger | nullrequiredcampaigns[].skipped_countinteger | nullrequiredRecipients skipped, e.g. because they opted outcampaigns[].created_atintegerrequiredUnix time in millisecondscampaigns[].started_atinteger | nullrequiredUnix time in milliseconds, or nullcampaigns[].completed_atinteger | nullrequiredUnix time in milliseconds, or nullcampaigns[].consent_attested_atinteger | nullrequiredWhen the sender attested consent, or null
- 401Missing, invalid or expired credentials
curl "https://api.chatkomodo.com/v1/blasts" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/blasts', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);import os
import requests
res = requests.get(
"https://api.chatkomodo.com/v1/blasts",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.chatkomodo.com/v1/blasts', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));{
"campaigns": [
{
"id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
"session_id": "YOUR_DEVICE_ID",
"api_key_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
"name": "Example",
"status": "active",
"message_type": "Hi Sam, your order has shipped.",
"body": "Hi Sam, your order has shipped.",
"total_recipients": 12,
"sent_count": 12,
"failed_count": 12,
"skipped_count": 12,
"created_at": 1767225600000,
"started_at": 1767225600000,
"completed_at": 1767225600000,
"consent_attested_at": 1767225600000
}
]
}