ChatKomodo Docs Home Sign in

API reference · Messaging

Messages

Send every kind of WhatsApp message, read the message history, retry, cancel, edit, react and unsend. See the sending guide for message types, statuses and pacing.

List message history

GET/v1/sessions/{sessionId}/messages

Paged history of the messages of a session, optionally filtered by chat, status, direction or text. Each page is ordered oldest first; to go further back pass next_cursor as cursor. Error texts are PII-redacted (numbers, JIDs, e-mail addresses) and cut to 160 characters.

Path parameters

  • sessionIdstringrequired
    Session (device) id

Query parameters

  • chat_idstring
    Only this chat (exact JID)
  • limitinteger
    Page size. Values outside 1-200 are clamped, not rejected
    Default: 50From 1 to 200
  • cursornumber
    created_at of the oldest message already seen; returns older messages only
  • statusstring
    Filter by status; unknown values are ignored
    One of: queued, sent, delivered, read, failed, cancelled, deleted, blocked
  • directionstring
    Filter by direction; other values are ignored
    One of: inbound, outbound
  • qstring
    Substring to find in the message text (first 200 characters are used)

Responses

  • 200A page of messages
    Response fields
    • messagesarray of objectrequired
      Oldest first
    • messages[].idstringrequired
      Gateway message id (ULID)
    • messages[].directionstringrequired
      One of: inbound, outbound
    • messages[].chat_idstringrequired
      Chat JID
    • messages[].message_typestringrequired
      text, image, video, document, audio, sticker, location, poll, contact or reaction
    • messages[].bodystring | nullrequired
      Text content, if any
    • messages[].media_r2_keystring | nullrequired
      Key of the stored media file; download it with GET /v1/sessions/{sessionId}/messages/{id}/media
    • messages[].statusstringrequired
      queued, sent, delivered, read, failed, cancelled or deleted
    • messages[].created_atintegerrequired
      Creation time (Unix epoch, milliseconds)
    • messages[].wa_message_idstring | nullrequired
      WhatsApp message id; null until the message was handed to WhatsApp
    • messages[].sent_atinteger | nullrequired
      Time the message was handed to WhatsApp (Unix epoch, milliseconds)
    • messages[].errorstring | nullrequired
      Failure reason of a failed message
    • has_morebooleanrequired
      true when the page is full, so older messages may exist
    • next_cursorinteger | nullrequired
      created_at of the oldest message in the page (Unix epoch, milliseconds): pass it as cursor for the next page. Null for an empty page
  • 401Missing or invalid credentials
  • 403The API key is restricted to other sessions
  • 404Unknown session, or a session of another tenant
Request
curl "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages?limit=50&status=failed" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages?limit=50&status=failed', {
  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/messages?limit=50&status=failed",
    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/messages?limit=50&status=failed', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "messages": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "direction": "inbound",
      "chat_id": "+447700900123",
      "message_type": "Hi Sam, your order has shipped.",
      "body": "Hi Sam, your order has shipped.",
      "media_r2_key": "uploads/abc123.jpg",
      "status": "active",
      "created_at": 1767225600000,
      "wa_message_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "sent_at": 1767225600000,
      "error": "string"
    }
  ],
  "has_more": true,
  "next_cursor": 1
}

Send a message

POST/v1/sessions/{sessionId}/messages

Send a WhatsApp message via the given session. Supports text, media (image/video/document/audio/sticker), location, poll, and contact. If the recipient has opted out the message is still sent and the response carries a RECIPIENT_OPTED_OUT warning, unless the tenant enabled enforce_api in the consent config, in which case the call fails with 409 RECIPIENT_OPTED_OUT.

Path parameters

  • sessionIdstringrequired

Headers

  • Idempotency-Keystring
    Any unique string (an order id, a UUID). Repeating a send with the same key returns the first result with idempotent_replay: true instead of sending again. Keys are unique per business and kept with the message (message retention).
    Up to 200 characters

Request body

  • chat_idstringrequired
    Recipient: a WhatsApp JID ([email protected], group …@g.us) or a phone number. Use the international format +<country code><number> (e.g. +44 7700 900123); a number in local format (07700 900123) is resolved against the business country from the profile, or against country_code when given.
  • typestringrequired
    What to send; each type needs its own field: text → body; image, video, audio, document, sticker → media_r2_key or url; location → location; poll → poll_name + 2–12 choices; contact → contact
    One of: text, image, document, audio, video, sticker, location, poll, contact
  • bodystring
    Text content. Required when type=text
    Up to 65 536 characters
  • captionstring
    Image/video caption
    Up to 1 024 characters
  • media_r2_keystring
    Key of a file uploaded with POST /v1/media/upload (or upload-url + PUT)
    Up to 256 characters
  • urlstring (uri)
    Public URL of the media to send instead of media_r2_key (downloaded server-side, max 25 MB)
    Up to 2 048 characters
  • filenamestring
    File name of a media file sent from url (its extension decides the stored file type)
    Up to 255 characters
  • locationobject
  • location.latnumberrequired
    From -90 to 90
  • location.lngnumberrequired
    From -180 to 180
  • poll_namestring
    Poll question (type=poll)
    Up to 200 characters
  • choicesarray of string
    Poll choices, 2–12 (type=poll)
    Up to 12 items
  • contactobject
    Contact card sent as a vCard (type=contact)
  • contact.namestringrequired
    1–100 characters
  • contact.phonestringrequired
    International format
  • reply_tostring
    ULID of message to reply to (quoted reply)
    ULID (26 characters)
  • connect_onlyboolean
    Default true: refuse with 409 CONFLICT while the device is not connected. false: accept and queue the message until the device reconnects
  • country_codestring
    Calling code (1-4 digits, e.g. 44) used only for local-format numbers; defaults to the business country. 0 never prefixes a code.

Responses

  • 200Message accepted
    Response fields
    • idstringrequired
      ULID (26 characters)
    • statusstringrequired
      One of: queued, sent, failed
    • idempotent_replayboolean
      true when an earlier request with the same Idempotency-Key was returned instead of sending again
    • warningsarray of object
      Non-fatal notices, e.g. RECIPIENT_OPTED_OUT (the message was still accepted; see enforce_api in the consent config)
    • warnings[].codestringrequired
    • warnings[].messagestringrequired
  • 400Validation error
  • 403Session not owned by tenant
  • 409Session not connected (CONFLICT) or recipient opted out in strict mode (RECIPIENT_OPTED_OUT)
  • 429Rate limited
Request
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "chat_id": "+447700900123",
  "type": "text",
  "body": "Hi Sam, your order #1042 has shipped 🚚"
}'
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "chat_id": "+447700900123",
    "type": "text",
    "body": "Hi Sam, your order #1042 has shipped 🚚"
  }),
});
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/messages",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "chat_id": "+447700900123",
        "type": "text",
        "body": "Hi Sam, your order #1042 has shipped 🚚",
    },
    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/messages', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'chat_id' => '+447700900123',
        'type' => 'text',
        'body' => 'Hi Sam, your order #1042 has shipped 🚚',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
  "status": "queued",
  "idempotent_replay": true,
  "warnings": [
    {
      "code": "RECIPIENT_OPTED_OUT",
      "message": "Hi Sam, your order has shipped."
    }
  ]
}

Get a message with its surroundings

GET/v1/sessions/{sessionId}/messages/{id}/context

Returns the message together with the messages around it from the same chat, oldest first. Messages with exactly the same timestamp as the anchor are left out.

Path parameters

  • sessionIdstringrequired
    Session (device) id
  • idstringrequired
    Gateway message id (the id of a message in the history)

Query parameters

  • beforeinteger
    Messages before the anchor (default 10, clamped to 0-50)
    From 0 to 50
  • afterinteger
    Messages after the anchor (default 10, clamped to 0-50)
    From 0 to 50

Responses

  • 200Anchor message and neighbours
    Response fields
    • chat_idstringrequired
    • messagesarray of objectrequired
      Oldest first, including the anchor
    • messages[].idstringrequired
      Gateway message id (ULID)
    • messages[].directionstringrequired
      One of: inbound, outbound
    • messages[].chat_idstringrequired
      Chat JID
    • messages[].message_typestringrequired
      text, image, video, document, audio, sticker, location, poll, contact or reaction
    • messages[].bodystring | nullrequired
      Text content, if any
    • messages[].media_r2_keystring | nullrequired
      Key of the stored media file; download it with GET /v1/sessions/{sessionId}/messages/{id}/media
    • messages[].statusstringrequired
      queued, sent, delivered, read, failed, cancelled or deleted
    • messages[].created_atintegerrequired
      Creation time (Unix epoch, milliseconds)
  • 401Missing or invalid credentials
  • 403The API key is restricted to other sessions
  • 404Message not found (NOT_FOUND); unknown session, or a session of another tenant
Request
curl "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/ID/context" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/ID/context', {
  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/messages/ID/context",
    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/messages/ID/context', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "chat_id": "+447700900123",
  "messages": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "direction": "inbound",
      "chat_id": "+447700900123",
      "message_type": "Hi Sam, your order has shipped.",
      "body": "Hi Sam, your order has shipped.",
      "media_r2_key": "uploads/abc123.jpg",
      "status": "active",
      "created_at": 1767225600000
    }
  ]
}

Download stored media

GET/v1/sessions/{sessionId}/messages/{id}/media

Returns the media file stored for a message as raw bytes. The response is always application/octet-stream; the original MIME type is not sent.

Path parameters

  • sessionIdstringrequired
    Session (device) id
  • idstringrequired
    Gateway message id (the id of a message in the history)

Responses

  • 200The media file (application/octet-stream)
  • 401Missing or invalid credentials
  • 403The API key is restricted to other sessions
  • 404Message not found, the message has no media, or the file is gone (NOT_FOUND); unknown session, or a session of another tenant
Request
curl "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/ID/media" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/ID/media', {
  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/messages/ID/media",
    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/messages/ID/media', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200

application/octet-stream — the file itself.

Cancel queued messages

POST/v1/sessions/{sessionId}/messages/cancel

Marks messages that are still queued as cancelled and removes them from the send queue. Messages in any other status are left alone; duplicate ids are ignored.

Path parameters

  • sessionIdstringrequired
    Session (device) id

Request body

  • idsarray of stringrequired
    Gateway message ids
    1–500 items

Responses

  • 200Result
    Response fields
    • cancelledintegerrequired
      Messages that were actually cancelled
  • 400Invalid body (VALIDATION_FAILED, INVALID_JSON)
  • 401Missing or invalid credentials
  • 403The API key is restricted to other sessions, or the operation is not available to CS-agent logins
  • 404Unknown session, or a session of another tenant
  • 429Strict rate limit exceeded: the plan's send rate per minute per API key (send_per_minute in GET /v1/usage; 30 on the Free plan), shared with every other POST whose path contains messages, including POST /messages. The response carries Retry-After (seconds) and retry_after (RATE_LIMITED)
Request
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/cancel" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "ids": [
    "01JA5V6Q8M2H7G4K9P3T0R1S2X"
  ]
}'
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/cancel', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "ids": [
      "01JA5V6Q8M2H7G4K9P3T0R1S2X"
    ]
  }),
});
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/messages/cancel",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "ids": [
            "01JA5V6Q8M2H7G4K9P3T0R1S2X",
        ],
    },
    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/messages/cancel', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'ids' => [
            '01JA5V6Q8M2H7G4K9P3T0R1S2X',
        ],
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "cancelled": 1
}

Delete messages from history

POST/v1/sessions/{sessionId}/messages/delete

Removes messages from the stored history and from the send queue. Nothing is unsent on WhatsApp; use /messages/unsend for that.

Path parameters

  • sessionIdstringrequired
    Session (device) id

Request body

  • idsarray of stringrequired
    Gateway message ids
    1–500 items

Responses

  • 200Result
    Response fields
    • deletedintegerrequired
      History rows removed
  • 400Invalid body (VALIDATION_FAILED, INVALID_JSON)
  • 401Missing or invalid credentials
  • 403The API key is restricted to other sessions, or the operation is not available to CS-agent logins
  • 404Unknown session, or a session of another tenant
  • 429Strict rate limit exceeded: the plan's send rate per minute per API key (send_per_minute in GET /v1/usage; 30 on the Free plan), shared with every other POST whose path contains messages, including POST /messages. The response carries Retry-After (seconds) and retry_after (RATE_LIMITED)
Request
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/delete" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "ids": [
    "01JA5V6Q8M2H7G4K9P3T0R1S2X"
  ]
}'
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/delete', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "ids": [
      "01JA5V6Q8M2H7G4K9P3T0R1S2X"
    ]
  }),
});
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/messages/delete",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "ids": [
            "01JA5V6Q8M2H7G4K9P3T0R1S2X",
        ],
    },
    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/messages/delete', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'ids' => [
            '01JA5V6Q8M2H7G4K9P3T0R1S2X',
        ],
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "deleted": 1
}

Unsend messages for everyone

POST/v1/sessions/{sessionId}/messages/unsend

Revokes your own sent messages on WhatsApp ("delete for everyone") and marks them deleted in the history. Each id is processed on its own and the call answers 200 even if some fail: check results[].error.

Path parameters

  • sessionIdstringrequired
    Session (device) id

Request body

  • idsarray of stringrequired
    Gateway message ids
    1–100 items

Responses

  • 200Per-message result
    Response fields
    • unsentintegerrequired
    • failedintegerrequired
    • resultsarray of objectrequired
    • results[].idstringrequired
    • results[].okbooleanrequired
    • results[].errorstring
      not_found: unknown id; not_sent: inbound message or not yet handed to WhatsApp; not_connected: session offline; wa_error: WhatsApp refused
      One of: not_found, not_sent, not_connected, wa_error
  • 400Invalid body (VALIDATION_FAILED, INVALID_JSON)
  • 401Missing or invalid credentials
  • 403The API key is restricted to other sessions, or the operation is not available to CS-agent logins
  • 404Unknown session, or a session of another tenant
  • 429Strict rate limit exceeded: the plan's send rate per minute per API key (send_per_minute in GET /v1/usage; 30 on the Free plan), shared with every other POST whose path contains messages, including POST /messages. The response carries Retry-After (seconds) and retry_after (RATE_LIMITED)
Request
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/unsend" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "ids": [
    "01JA5V6Q8M2H7G4K9P3T0R1S2X"
  ]
}'
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/unsend', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "ids": [
      "01JA5V6Q8M2H7G4K9P3T0R1S2X"
    ]
  }),
});
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/messages/unsend",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "ids": [
            "01JA5V6Q8M2H7G4K9P3T0R1S2X",
        ],
    },
    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/messages/unsend', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'ids' => [
            '01JA5V6Q8M2H7G4K9P3T0R1S2X',
        ],
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "unsent": 12,
  "failed": 1,
  "results": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "ok": true,
      "error": "not_found"
    }
  ]
}

Forward a message to another chat

POST/v1/sessions/{sessionId}/messages/{msgId}/forward

Sends the stored type, text and media key of a logged message to another chat as a new message. It goes through the same opt-out check as POST /messages: an opted-out recipient yields a RECIPIENT_OPTED_OUT warning, or a 409 when strict mode (enforce_api) is on.

Path parameters

  • sessionIdstringrequired
    Session (device) id
  • msgIdstringrequired
    Gateway message id to forward

Request body

  • to_chat_idstringrequired
    Destination: a JID or a phone number (local-format numbers are resolved against the business country)

Responses

  • 200Forwarded
    Response fields
    • message_idstringrequired
      Id of the new message
    • okbooleanrequired
    • warningsarray of object
      Present when the recipient has opted out (RECIPIENT_OPTED_OUT): the message was still accepted because strict mode (enforce_api) is off
    • warnings[].codestringrequired
    • warnings[].messagestringrequired
  • 400to_chat_id missing (VALIDATION_FAILED), or invalid (INVALID_NUMBER, INVALID_JID)
  • 401Missing or invalid credentials
  • 403The API key is restricted to other sessions
  • 404The message was not found in this session (no code); unknown session, or a session of another tenant
  • 409The session is not connected, or the recipient opted out in strict mode (RECIPIENT_OPTED_OUT)
  • 429Strict rate limit exceeded: the plan's send rate per minute per API key (send_per_minute in GET /v1/usage; 30 on the Free plan), shared with every other POST whose path contains messages, including POST /messages. The response carries Retry-After (seconds) and retry_after (RATE_LIMITED)
  • 500Sending or the WhatsApp call failed (code is usually INTERNAL)
Request
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/forward" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "to_chat_id": "+12025550123"
}'
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/forward', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "to_chat_id": "+12025550123"
  }),
});
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/messages/MSG_ID/forward",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "to_chat_id": "+12025550123",
    },
    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/messages/MSG_ID/forward', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'to_chat_id' => '+12025550123',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "message_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
  "ok": true,
  "warnings": [
    {
      "code": "RECIPIENT_OPTED_OUT",
      "message": "Hi Sam, your order has shipped."
    }
  ]
}

Resend an outbound message

POST/v1/sessions/{sessionId}/messages/{msgId}/retry

Sends a logged outbound message again as a new message with a new id. Only text, image, document, audio, video and sticker messages that still have their content can be retried. The same opt-out check as POST /messages applies, and the send is paced like any other.

Path parameters

  • sessionIdstringrequired
    Session (device) id
  • msgIdstringrequired
    Gateway message id to resend

Responses

  • 200Resend accepted
    Response fields
    • message_idstringrequired
      Id of the new message
    • statusstringrequired
      sent, queued (paced), blocked (recipient on the session blocklist) or quota_exceeded (monthly plan quota used up)
    • warningsarray of object
      Present when the recipient has opted out (RECIPIENT_OPTED_OUT): the message was still accepted because strict mode (enforce_api) is off
    • warnings[].codestringrequired
    • warnings[].messagestringrequired
  • 400The message is inbound, of an unsupported type or has no content (NOT_RETRYABLE)
  • 401Missing or invalid credentials
  • 403The API key is restricted to other sessions
  • 404Message not found (NOT_FOUND); unknown session, or a session of another tenant
  • 409The session is not connected (CONFLICT), or the recipient opted out in strict mode (RECIPIENT_OPTED_OUT)
  • 429Strict rate limit exceeded: the plan's send rate per minute per API key (send_per_minute in GET /v1/usage; 30 on the Free plan), shared with every other POST whose path contains messages, including POST /messages. The response carries Retry-After (seconds) and retry_after (RATE_LIMITED)
  • 500Sending or the WhatsApp call failed (code is usually INTERNAL)
Request
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/retry" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/retry', {
  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/messages/MSG_ID/retry",
    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/messages/MSG_ID/retry', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "message_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
  "status": "active",
  "warnings": [
    {
      "code": "RECIPIENT_OPTED_OUT",
      "message": "Hi Sam, your order has shipped."
    }
  ]
}

Delete a sent message for everyone

POST/v1/sessions/{sessionId}/messages/{msgId}/delete

Revokes one of your sent messages on WhatsApp and marks it deleted in the history. The message must already have a WhatsApp id, i.e. it was handed to WhatsApp.

Path parameters

  • sessionIdstringrequired
    Session (device) id
  • msgIdstringrequired
    Gateway message id (the id of a message in the history)

Responses

  • 200Revoked
    Response fields
    • okbooleanrequired
  • 401Missing or invalid credentials
  • 403The API key is restricted to other sessions
  • 404Message not found, or it has no WhatsApp id yet; unknown session, or a session of another tenant
  • 409The session is not connected
  • 429Strict rate limit exceeded: the plan's send rate per minute per API key (send_per_minute in GET /v1/usage; 30 on the Free plan), shared with every other POST whose path contains messages, including POST /messages. The response carries Retry-After (seconds) and retry_after (RATE_LIMITED)
  • 500Sending or the WhatsApp call failed (code is usually INTERNAL)
Request
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/delete" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/delete', {
  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/messages/MSG_ID/delete",
    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/messages/MSG_ID/delete', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "ok": true
}

Edit a sent message

POST/v1/sessions/{sessionId}/messages/{msgId}/edit

Replaces the text of one of your sent messages on WhatsApp and updates the stored body. msgId is the gateway message id; the message must already have a WhatsApp id.

Path parameters

  • sessionIdstringrequired
    Session (device) id
  • msgIdstringrequired
    Gateway message id (the id of a message in the history)

Request body

  • bodystringrequired
    New text

Responses

  • 200Edited
    Response fields
    • okbooleanrequired
  • 401Missing or invalid credentials
  • 403The API key is restricted to other sessions
  • 404Message not found, or it has no WhatsApp id yet; unknown session, or a session of another tenant
  • 409The session is not connected
  • 429Strict rate limit exceeded: the plan's send rate per minute per API key (send_per_minute in GET /v1/usage; 30 on the Free plan), shared with every other POST whose path contains messages, including POST /messages. The response carries Retry-After (seconds) and retry_after (RATE_LIMITED)
  • 500Sending or the WhatsApp call failed (code is usually INTERNAL)
Request
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/edit" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "body": "Hi Sam, your order has shipped."
}'
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/edit', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "body": "Hi Sam, your order has shipped."
  }),
});
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/messages/MSG_ID/edit",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "body": "Hi Sam, your order has shipped.",
    },
    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/messages/MSG_ID/edit', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'body' => 'Hi Sam, your order has shipped.',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "ok": true
}

Edit a message by WhatsApp id

PUT/v1/sessions/{sessionId}/messages/{msgId}/edit

Edits a message addressed by its WhatsApp message id instead of the gateway id. Unlike POST …/messages/{msgId}/edit it does not look the message up and does not update the stored history.

Path parameters

  • sessionIdstringrequired
    Session (device) id
  • msgIdstringrequired
    WhatsApp message id (wa_message_id), not the gateway id

Request body

  • chat_idstringrequired
    JID of the chat the message is in
  • new_textstringrequired
    New text

Responses

  • 200Edited
    Response fields
    • okbooleanrequired
  • 400The body is not valid JSON
  • 401Missing or invalid credentials
  • 403The API key is restricted to other sessions
  • 404Unknown session, or a session of another tenant
  • 409The session is not connected
  • 500WhatsApp operation failed: WhatsApp or the library rejected the call. The gateway does not validate this body, so missing or malformed fields end up here too
Request
curl -X PUT "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/edit" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "chat_id": "+447700900123",
  "new_text": "Hi Sam, your order has shipped."
}'
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/edit', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "chat_id": "+447700900123",
    "new_text": "Hi Sam, your order has shipped."
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.put(
    "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/edit",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "chat_id": "+447700900123",
        "new_text": "Hi Sam, your order has shipped.",
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('PUT', 'https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/edit', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'chat_id' => '+447700900123',
        'new_text' => 'Hi Sam, your order has shipped.',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "ok": true
}

React to a message

POST/v1/sessions/{sessionId}/messages/{msgId}/react

Sends an emoji reaction to a message through the normal send pipeline. msgId may be the gateway id or the WhatsApp id of the message.

Path parameters

  • sessionIdstringrequired
    Session (device) id
  • msgIdstringrequired
    Gateway message id or WhatsApp message id

Request body

  • emojistring
    Reaction emoji; defaults to 👍 when omitted

Responses

  • 200Reaction sent
    Response fields
    • okbooleanrequired
  • 401Missing or invalid credentials
  • 403The API key is restricted to other sessions
  • 404Message not found; unknown session, or a session of another tenant
  • 409The session is not connected
  • 429Strict rate limit exceeded: the plan's send rate per minute per API key (send_per_minute in GET /v1/usage; 30 on the Free plan), shared with every other POST whose path contains messages, including POST /messages. The response carries Retry-After (seconds) and retry_after (RATE_LIMITED)
Request
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/react" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "emoji": "👍"
}'
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/react', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "emoji": "👍"
  }),
});
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/messages/MSG_ID/react",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "emoji": "👍",
    },
    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/messages/MSG_ID/react', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'emoji' => '👍',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "ok": true
}

Mark a message as read

POST/v1/sessions/{sessionId}/messages/{msgId}/read

Sends a read receipt for the message to WhatsApp. The local unread counter of the inbox is not changed; use POST …/inbox/{chatId}/read for that.

Path parameters

  • sessionIdstringrequired
    Session (device) id
  • msgIdstringrequired
    Gateway message id (the id of a message in the history)

Responses

  • 200Read receipt sent
    Response fields
    • okbooleanrequired
  • 401Missing or invalid credentials
  • 403The API key is restricted to other sessions
  • 404Message not found, or it has no WhatsApp id yet; unknown session, or a session of another tenant
  • 409The session is not connected
  • 429Strict rate limit exceeded: the plan's send rate per minute per API key (send_per_minute in GET /v1/usage; 30 on the Free plan), shared with every other POST whose path contains messages, including POST /messages. The response carries Retry-After (seconds) and retry_after (RATE_LIMITED)
  • 500Sending or the WhatsApp call failed (code is usually INTERNAL)
Request
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/read" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/MSG_ID/read', {
  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/messages/MSG_ID/read",
    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/messages/MSG_ID/read', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "ok": true
}

Send a buttons or list message

POST/v1/sessions/{sessionId}/messages/interactive

Sends a button or list message straight through the WhatsApp connection. Unlike POST /messages it is not queued or paced, not written to the message history and not checked against the opt-out list, and chat_id must already be a JID. A type other than buttons or list sends nothing but still answers 200.

Path parameters

  • sessionIdstringrequired
    Session (device) id

Request body

  • chat_idstringrequired
    Recipient JID
  • typestringrequired
    One of: buttons, list
  • bodystringrequired
    Message text
  • footerstring
  • buttonsarray of object
    For buttons: the buttons, in order (button ids are generated as btn_0, btn_1, …)
  • buttons[].textstringrequired
    Button label
  • titlestring
    For list: list title
  • sectionsarray of object
    For list: sections with rows, passed unchanged to the WhatsApp library. The list button is labelled Menu
  • sections[].titlestring
  • sections[].rowsarray of objectrequired
  • sections[].rows[].titlestringrequired
  • sections[].rows[].rowIdstringrequired
  • sections[].rows[].descriptionstring

Responses

  • 200Accepted
    Response fields
    • okbooleanrequired
  • 400The body is not valid JSON
  • 401Missing or invalid credentials
  • 403The API key is restricted to other sessions
  • 404Unknown session, or a session of another tenant
  • 409The session is not connected
  • 429Strict rate limit exceeded: the plan's send rate per minute per API key (send_per_minute in GET /v1/usage; 30 on the Free plan), shared with every other POST whose path contains messages, including POST /messages. The response carries Retry-After (seconds) and retry_after (RATE_LIMITED)
  • 500WhatsApp operation failed: WhatsApp or the library rejected the call. The gateway does not validate this body, so missing or malformed fields end up here too
Request
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/interactive" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "chat_id": "+447700900123",
  "type": "buttons",
  "body": "Your order #1042 is ready. What next?",
  "footer": "Acme Store",
  "buttons": [
    {
      "text": "Track my order"
    },
    {
      "text": "Talk to us"
    }
  ]
}'
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/messages/interactive', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "chat_id": "+447700900123",
    "type": "buttons",
    "body": "Your order #1042 is ready. What next?",
    "footer": "Acme Store",
    "buttons": [
      {
        "text": "Track my order"
      },
      {
        "text": "Talk to us"
      }
    ]
  }),
});
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/messages/interactive",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "chat_id": "+447700900123",
        "type": "buttons",
        "body": "Your order #1042 is ready. What next?",
        "footer": "Acme Store",
        "buttons": [
            {
                "text": "Track my order",
            },
            {
                "text": "Talk to us",
            },
        ],
    },
    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/messages/interactive', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'chat_id' => '+447700900123',
        'type' => 'buttons',
        'body' => 'Your order #1042 is ready. What next?',
        'footer' => 'Acme Store',
        'buttons' => [
            [
                'text' => 'Track my order',
            ],
            [
                'text' => 'Talk to us',
            ],
        ],
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "ok": true
}

List messages across all devices

GET/v1/messages

Message log of all the tenant's devices, newest first, filtered and paged with a cursor. Filters with an unrecognised value (direction, status) are ignored rather than rejected. Pass the returned next_cursor as cursor for the next page.

Query parameters

  • limitinteger
    Values outside 1-1 000 are clamped
    Default: 100From 1 to 1 000
  • sincestring
    Only messages created at or after this time: Unix ms or a date string
  • untilstring
    Only messages created before this time: Unix ms or a date string
  • session_idstring
    Only this device
  • directionstring
    One of: inbound, outbound
  • statusstring
    One of: queued, sent, delivered, read, failed, cancelled, deleted, blocked
  • cursorstring
    Opaque cursor <created_at>:<id> from a previous next_cursor

Responses

  • 200Messages
    Response fields
    • messagesarray of objectrequired
    • messages[].idstringrequired
    • messages[].session_idstringrequired
    • messages[].chat_idstringrequired
    • messages[].directionstringrequired
      One of: inbound, outbound
    • messages[].message_typestringrequired
    • messages[].statusstringrequired
    • messages[].bodystring | nullrequired
    • messages[].created_atintegerrequired
      Unix time in milliseconds
    • next_cursorstring | nullrequired
      null on the last page
  • 401Missing, invalid or expired credentials
Request
curl "https://api.chatkomodo.com/v1/messages?since=1767225600000&limit=1000" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/messages?since=1767225600000&limit=1000', {
  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/messages?since=1767225600000&limit=1000",
    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/messages?since=1767225600000&limit=1000', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "messages": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "session_id": "YOUR_DEVICE_ID",
      "chat_id": "+447700900123",
      "direction": "inbound",
      "message_type": "Hi Sam, your order has shipped.",
      "status": "active",
      "body": "Hi Sam, your order has shipped.",
      "created_at": 1767225600000
    }
  ],
  "next_cursor": "string"
}