Code samples use your device
. Create an API key under API keys.API reference · Messaging
Recurring messages
Messages repeated daily, weekly, monthly or every N days.
List recurring messages
GET
/v1/recurringRecurring messages of the tenant, newest first (max 1 000).
Responses
- 200Recurring messages
Response fields
recurringarray of objectrequiredrecurring[].idstringrequiredrecurring[].session_idstringrequiredDevice the message is sent fromrecurring[].namestringrequiredrecurring[].targetsarray of stringrequiredRecipient JIDs (phone numbers are normalised to JIDs when saved)recurring[].messagestring | nullrequiredrecurring[].media_r2_keystring | nullrequiredrecurring[].schedule_typestringrequiredrecurring[].schedule_configobjectrequiredrecurring[].schedule_config.hourinteger | nullHour of day in the schedule timezone (default 9)recurring[].schedule_config.minuteinteger | nullMinute (default 0)recurring[].schedule_config.dayinteger | nullweekly: weekday 0-6 with 0 = Sunday (default 1).monthly: day of month 1-31, clamped to the month's last day (default 1)recurring[].schedule_config.every_daysintegerinterval: run every N days (default 1)recurring[].timezonestringrequiredIANA timezone the schedule is evaluated inrecurring[].next_run_atinteger | nullrequiredNext planned run, Unix msrecurring[].last_run_atinteger | nullrequiredLast run, Unix msrecurring[].is_activebooleanrequiredrecurring[].created_atintegerrequiredUnix time in milliseconds
- 401Missing, invalid or expired credentials
- 403Forbidden for an API key restricted to specific sessions (
KEY_SCOPE_FORBIDDEN)
Request
curl "https://api.chatkomodo.com/v1/recurring" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/recurring', {
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/recurring",
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/recurring', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));Response 200
{
"recurring": [
{
"id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
"session_id": "YOUR_DEVICE_ID",
"name": "Monday reminder",
"targets": [
"+447700900123"
],
"message": "Hi Sam, your order has shipped.",
"media_r2_key": "uploads/abc123.jpg",
"schedule_type": "daily",
"schedule_config": {
"hour": 1,
"minute": 1,
"day": 1,
"every_days": 1
},
"timezone": "Asia/Jakarta",
"next_run_at": 1767225600000,
"last_run_at": 1767225600000,
"is_active": true,
"created_at": 1767225600000
}
]
}Create a recurring message
POST
/v1/recurringSchedules a message to be sent to a list of recipients every day, week, month or N days. next_run_at is computed by the server in the schedule timezone. These sends are not held back by the blast send window.
Request body
namestringLabel, 1-100 characterssession_idstringrequiredDevice to send from; must belong to the tenanttargetsarray of stringrequiredPhone numbers (international format; local numbers use the business country) or JIDs, 1-1 000. Duplicates are removed; any invalid entry rejects the request withINVALID_TARGETSmessagestring | nullText to send (max 4 096 characters). Eithermessageormedia_r2_keyis requiredmedia_r2_keystring | nullKey of an uploaded media file to sendschedule_typestringrequiredschedule_configobjectschedule_config.hourinteger | nullHour of day in the schedule timezone (default 9)schedule_config.minuteinteger | nullMinute (default 0)schedule_config.dayinteger | nullweekly: weekday 0-6 with 0 = Sunday (default 1).monthly: day of month 1-31, clamped to the month's last day (default 1)schedule_config.every_daysintegerinterval: run every N days (default 1)timezonestringIANA timezone (default: the business timezone from the profile, elseAsia/Jakarta)is_activebooleanDefaulttrue. Also accepts 1/0 and the stringstrue/false/1/0
Responses
- 201Created
Response fields
idstringrequiredsession_idstringrequiredDevice the message is sent fromnamestringrequiredtargetsarray of stringrequiredRecipient JIDs (phone numbers are normalised to JIDs when saved)messagestring | nullrequiredmedia_r2_keystring | nullrequiredschedule_typestringrequiredschedule_configobjectrequiredschedule_config.hourinteger | nullHour of day in the schedule timezone (default 9)schedule_config.minuteinteger | nullMinute (default 0)schedule_config.dayinteger | nullweekly: weekday 0-6 with 0 = Sunday (default 1).monthly: day of month 1-31, clamped to the month's last day (default 1)schedule_config.every_daysintegerinterval: run every N days (default 1)timezonestringrequiredIANA timezone the schedule is evaluated innext_run_atinteger | nullrequiredNext planned run, Unix mslast_run_atinteger | nullrequiredLast run, Unix msis_activebooleanrequiredcreated_atintegerrequiredUnix time in milliseconds
- 400
VALIDATION_FAILED(bad body, unknown timezone,schedule_config.dayout of range, or neithermessagenormedia_r2_key) orINVALID_TARGETS - 401Missing, invalid or expired credentials
- 403Forbidden for a customer-service agent login (
Agent access is not allowed for this operation) and an API key restricted to specific sessions (KEY_SCOPE_FORBIDDEN) - 404Session not found or not yours (
NOT_FOUND)
Request
curl -X POST "https://api.chatkomodo.com/v1/recurring" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Monday reminder",
"session_id": "YOUR_DEVICE_ID",
"targets": [
"+447700900123",
"+12025550123"
],
"message": "Weekly reminder: team meeting at 09:00.",
"schedule_type": "weekly",
"schedule_config": {
"day": 1,
"hour": 8,
"minute": 30
},
"timezone": "Asia/Jakarta"
}'const res = await fetch('https://api.chatkomodo.com/v1/recurring', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Monday reminder",
"session_id": "YOUR_DEVICE_ID",
"targets": [
"+447700900123",
"+12025550123"
],
"message": "Weekly reminder: team meeting at 09:00.",
"schedule_type": "weekly",
"schedule_config": {
"day": 1,
"hour": 8,
"minute": 30
},
"timezone": "Asia/Jakarta"
}),
});
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/recurring",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"name": "Monday reminder",
"session_id": "YOUR_DEVICE_ID",
"targets": [
"+447700900123",
"+12025550123",
],
"message": "Weekly reminder: team meeting at 09:00.",
"schedule_type": "weekly",
"schedule_config": {
"day": 1,
"hour": 8,
"minute": 30,
},
"timezone": "Asia/Jakarta",
},
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/recurring', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'name' => 'Monday reminder',
'session_id' => 'YOUR_DEVICE_ID',
'targets' => [
'+447700900123',
'+12025550123',
],
'message' => 'Weekly reminder: team meeting at 09:00.',
'schedule_type' => 'weekly',
'schedule_config' => [
'day' => 1,
'hour' => 8,
'minute' => 30,
],
'timezone' => 'Asia/Jakarta',
],
]);
print_r(json_decode((string) $res->getBody(), true));Response 201
{
"id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
"session_id": "YOUR_DEVICE_ID",
"name": "Monday reminder",
"targets": [
"+447700900123"
],
"message": "Hi Sam, your order has shipped.",
"media_r2_key": "uploads/abc123.jpg",
"schedule_type": "daily",
"schedule_config": {
"hour": 1,
"minute": 1,
"day": 1,
"every_days": 1
},
"timezone": "Asia/Jakarta",
"next_run_at": 1767225600000,
"last_run_at": 1767225600000,
"is_active": true,
"created_at": 1767225600000
}Edit a recurring message
PATCH
/v1/recurring/{id}Changes any of the fields given at creation (send only those to change). next_run_at is recomputed on every edit. The message must still have a message or a media_r2_key afterwards. Use is_active: false to pause.
Path parameters
idstringrequiredRecurring message id
Request body
namestringLabel, 1-100 characterssession_idstringDevice to send from; must belong to the tenanttargetsarray of stringPhone numbers (international format; local numbers use the business country) or JIDs, 1-1 000. Duplicates are removed; any invalid entry rejects the request withINVALID_TARGETSmessagestring | nullText to send (max 4 096 characters). Eithermessageormedia_r2_keyis requiredmedia_r2_keystring | nullKey of an uploaded media file to sendschedule_typestringschedule_configobjectschedule_config.hourinteger | nullHour of day in the schedule timezone (default 9)schedule_config.minuteinteger | nullMinute (default 0)schedule_config.dayinteger | nullweekly: weekday 0-6 with 0 = Sunday (default 1).monthly: day of month 1-31, clamped to the month's last day (default 1)schedule_config.every_daysintegerinterval: run every N days (default 1)timezonestringIANA timezone (default: the business timezone from the profile, elseAsia/Jakarta)is_activebooleanAlso accepts 1/0 and the stringstrue/false/1/0
Responses
- 200Updated
Response fields
okbooleanrequiredrecurringobjectrequiredrecurring.idstringrequiredrecurring.session_idstringrequiredDevice the message is sent fromrecurring.namestringrequiredrecurring.targetsarray of stringrequiredRecipient JIDs (phone numbers are normalised to JIDs when saved)recurring.messagestring | nullrequiredrecurring.media_r2_keystring | nullrequiredrecurring.schedule_typestringrequiredrecurring.schedule_configobjectrequiredrecurring.schedule_config.hourinteger | nullHour of day in the schedule timezone (default 9)recurring.schedule_config.minuteinteger | nullMinute (default 0)recurring.schedule_config.dayinteger | nullweekly: weekday 0-6 with 0 = Sunday (default 1).monthly: day of month 1-31, clamped to the month's last day (default 1)recurring.schedule_config.every_daysintegerinterval: run every N days (default 1)recurring.timezonestringrequiredIANA timezone the schedule is evaluated inrecurring.next_run_atinteger | nullrequiredNext planned run, Unix msrecurring.last_run_atinteger | nullrequiredLast run, Unix msrecurring.is_activebooleanrequiredrecurring.created_atintegerrequiredUnix time in milliseconds
- 400
VALIDATION_FAILED(bad body, unknown timezone,schedule_config.dayout of range, or neithermessagenormedia_r2_key) orINVALID_TARGETS - 401Missing, invalid or expired credentials
- 403Forbidden for a customer-service agent login (
Agent access is not allowed for this operation) and an API key restricted to specific sessions (KEY_SCOPE_FORBIDDEN) - 404Recurring message not found, or the new
session_idis not yours (NOT_FOUND)
Request
curl -X PATCH "https://api.chatkomodo.com/v1/recurring/ID" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "Weekly reminder: team meeting moved to 10:00.",
"is_active": true
}'const res = await fetch('https://api.chatkomodo.com/v1/recurring/ID', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"message": "Weekly reminder: team meeting moved to 10:00.",
"is_active": 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.patch(
"https://api.chatkomodo.com/v1/recurring/ID",
headers={
"Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
},
json={
"message": "Weekly reminder: team meeting moved to 10:00.",
"is_active": True,
},
timeout=30,
)
res.raise_for_status()
print(res.json())<?php
require 'vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->request('PATCH', 'https://api.chatkomodo.com/v1/recurring/ID', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
'json' => [
'message' => 'Weekly reminder: team meeting moved to 10:00.',
'is_active' => true,
],
]);
print_r(json_decode((string) $res->getBody(), true));Response 200
{
"ok": true,
"recurring": {
"id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
"session_id": "YOUR_DEVICE_ID",
"name": "Monday reminder",
"targets": [
"+447700900123"
],
"message": "Hi Sam, your order has shipped.",
"media_r2_key": "uploads/abc123.jpg",
"schedule_type": "daily",
"schedule_config": {
"hour": 1,
"minute": 1,
"day": 1,
"every_days": 1
},
"timezone": "Asia/Jakarta",
"next_run_at": 1767225600000,
"last_run_at": 1767225600000,
"is_active": true,
"created_at": 1767225600000
}
}Delete a recurring message
DEL
/v1/recurring/{id}Permanently deletes a recurring message.
Path parameters
idstringrequiredRecurring message id
Responses
- 200Deleted
Response fields
okbooleanrequired
- 401Missing, invalid or expired credentials
- 403Forbidden for a customer-service agent login (
Agent access is not allowed for this operation) and an API key restricted to specific sessions (KEY_SCOPE_FORBIDDEN) - 404Recurring message not found (
NOT_FOUND)
Request
curl -X DELETE "https://api.chatkomodo.com/v1/recurring/ID" \
-H "Authorization: Bearer $CHATKOMODO_API_KEY"const res = await fetch('https://api.chatkomodo.com/v1/recurring/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/recurring/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/recurring/ID', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
],
]);
print_r(json_decode((string) $res->getBody(), true));Response 200
{
"ok": true
}