ChatKomodo Docs Home Sign in

API reference · Messaging

Recurring messages

Messages repeated daily, weekly, monthly or every N days.

List recurring messages

GET/v1/recurring

Recurring messages of the tenant, newest first (max 1 000).

Responses

  • 200Recurring messages
    Response fields
    • recurringarray of objectrequired
    • recurring[].idstringrequired
    • recurring[].session_idstringrequired
      Device the message is sent from
    • recurring[].namestringrequired
    • recurring[].targetsarray of stringrequired
      Recipient JIDs (phone numbers are normalised to JIDs when saved)
    • recurring[].messagestring | nullrequired
    • recurring[].media_r2_keystring | nullrequired
    • recurring[].schedule_typestringrequired
      One of: daily, weekly, monthly, interval
    • recurring[].schedule_configobjectrequired
    • recurring[].schedule_config.hourinteger | null
      Hour of day in the schedule timezone (default 9)
      From 0 to 23
    • recurring[].schedule_config.minuteinteger | null
      Minute (default 0)
      From 0 to 59
    • recurring[].schedule_config.dayinteger | null
      weekly: weekday 0-6 with 0 = Sunday (default 1). monthly: day of month 1-31, clamped to the month's last day (default 1)
      From 0 to 31
    • recurring[].schedule_config.every_daysinteger
      interval: run every N days (default 1)
      From 1 to 365
    • recurring[].timezonestringrequired
      IANA timezone the schedule is evaluated in
    • recurring[].next_run_atinteger | nullrequired
      Next planned run, Unix ms
    • recurring[].last_run_atinteger | nullrequired
      Last run, Unix ms
    • recurring[].is_activebooleanrequired
    • recurring[].created_atintegerrequired
      Unix 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/recurring

Schedules 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

  • namestring
    Label, 1-100 characters
    Default: "Untitled"1–100 characters
  • session_idstringrequired
    Device to send from; must belong to the tenant
    1–64 characters
  • targetsarray of stringrequired
    Phone numbers (international format; local numbers use the business country) or JIDs, 1-1 000. Duplicates are removed; any invalid entry rejects the request with INVALID_TARGETS
    1–1 000 items
  • messagestring | null
    Text to send (max 4 096 characters). Either message or media_r2_key is required
    Up to 4 096 characters
  • media_r2_keystring | null
    Key of an uploaded media file to send
    Up to 256 characters
  • schedule_typestringrequired
    One of: daily, weekly, monthly, interval
  • schedule_configobject
  • schedule_config.hourinteger | null
    Hour of day in the schedule timezone (default 9)
    From 0 to 23
  • schedule_config.minuteinteger | null
    Minute (default 0)
    From 0 to 59
  • schedule_config.dayinteger | null
    weekly: weekday 0-6 with 0 = Sunday (default 1). monthly: day of month 1-31, clamped to the month's last day (default 1)
    From 0 to 31
  • schedule_config.every_daysinteger
    interval: run every N days (default 1)
    From 1 to 365
  • timezonestring
    IANA timezone (default: the business timezone from the profile, else Asia/Jakarta)
    1–64 characters
  • is_activeboolean
    Default true. Also accepts 1/0 and the strings true/false/1/0

Responses

  • 201Created
    Response fields
    • idstringrequired
    • session_idstringrequired
      Device the message is sent from
    • namestringrequired
    • targetsarray of stringrequired
      Recipient JIDs (phone numbers are normalised to JIDs when saved)
    • messagestring | nullrequired
    • media_r2_keystring | nullrequired
    • schedule_typestringrequired
      One of: daily, weekly, monthly, interval
    • schedule_configobjectrequired
    • schedule_config.hourinteger | null
      Hour of day in the schedule timezone (default 9)
      From 0 to 23
    • schedule_config.minuteinteger | null
      Minute (default 0)
      From 0 to 59
    • schedule_config.dayinteger | null
      weekly: weekday 0-6 with 0 = Sunday (default 1). monthly: day of month 1-31, clamped to the month's last day (default 1)
      From 0 to 31
    • schedule_config.every_daysinteger
      interval: run every N days (default 1)
      From 1 to 365
    • timezonestringrequired
      IANA timezone the schedule is evaluated in
    • next_run_atinteger | nullrequired
      Next planned run, Unix ms
    • last_run_atinteger | nullrequired
      Last run, Unix ms
    • is_activebooleanrequired
    • created_atintegerrequired
      Unix time in milliseconds
  • 400VALIDATION_FAILED (bad body, unknown timezone, schedule_config.day out of range, or neither message nor media_r2_key) or INVALID_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

  • idstringrequired
    Recurring message id

Request body

  • namestring
    Label, 1-100 characters
    1–100 characters
  • session_idstring
    Device to send from; must belong to the tenant
    1–64 characters
  • targetsarray of string
    Phone numbers (international format; local numbers use the business country) or JIDs, 1-1 000. Duplicates are removed; any invalid entry rejects the request with INVALID_TARGETS
    1–1 000 items
  • messagestring | null
    Text to send (max 4 096 characters). Either message or media_r2_key is required
    Up to 4 096 characters
  • media_r2_keystring | null
    Key of an uploaded media file to send
    Up to 256 characters
  • schedule_typestring
    One of: daily, weekly, monthly, interval
  • schedule_configobject
  • schedule_config.hourinteger | null
    Hour of day in the schedule timezone (default 9)
    From 0 to 23
  • schedule_config.minuteinteger | null
    Minute (default 0)
    From 0 to 59
  • schedule_config.dayinteger | null
    weekly: weekday 0-6 with 0 = Sunday (default 1). monthly: day of month 1-31, clamped to the month's last day (default 1)
    From 0 to 31
  • schedule_config.every_daysinteger
    interval: run every N days (default 1)
    From 1 to 365
  • timezonestring
    IANA timezone (default: the business timezone from the profile, else Asia/Jakarta)
    1–64 characters
  • is_activeboolean
    Also accepts 1/0 and the strings true/false/1/0

Responses

  • 200Updated
    Response fields
    • okbooleanrequired
    • recurringobjectrequired
    • recurring.idstringrequired
    • recurring.session_idstringrequired
      Device the message is sent from
    • recurring.namestringrequired
    • recurring.targetsarray of stringrequired
      Recipient JIDs (phone numbers are normalised to JIDs when saved)
    • recurring.messagestring | nullrequired
    • recurring.media_r2_keystring | nullrequired
    • recurring.schedule_typestringrequired
      One of: daily, weekly, monthly, interval
    • recurring.schedule_configobjectrequired
    • recurring.schedule_config.hourinteger | null
      Hour of day in the schedule timezone (default 9)
      From 0 to 23
    • recurring.schedule_config.minuteinteger | null
      Minute (default 0)
      From 0 to 59
    • recurring.schedule_config.dayinteger | null
      weekly: weekday 0-6 with 0 = Sunday (default 1). monthly: day of month 1-31, clamped to the month's last day (default 1)
      From 0 to 31
    • recurring.schedule_config.every_daysinteger
      interval: run every N days (default 1)
      From 1 to 365
    • recurring.timezonestringrequired
      IANA timezone the schedule is evaluated in
    • recurring.next_run_atinteger | nullrequired
      Next planned run, Unix ms
    • recurring.last_run_atinteger | nullrequired
      Last run, Unix ms
    • recurring.is_activebooleanrequired
    • recurring.created_atintegerrequired
      Unix time in milliseconds
  • 400VALIDATION_FAILED (bad body, unknown timezone, schedule_config.day out of range, or neither message nor media_r2_key) or INVALID_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_id is 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

  • idstringrequired
    Recurring 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
}