ChatKomodo Docs Home Sign in

API reference · Customers

Labels & leads

Labels on people and chats (several per chat, shared with the CRM contact), automatic labelling rules (keywords, new chats, store orders, inactivity, AI) and the leads board with your own stages. See the labels & leads guide.

List labels

GET/v1/labels

Every label of the business (used on contacts or chats, or created here) with its colour, description and how many contacts and group chats carry it. A chat with a person shares its labels with that person's CRM contact, so the same labels drive segments, broadcasts and the leads board.

Responses

  • 200Labels
    Response fields
    • labelsarray of objectrequired
    • labels[].namestringrequired
      Normalised name: lowercase, spaces become dashes
    • labels[].colorstringrequired
      #RRGGBB
    • labels[].descriptionstring | nullrequired
    • labels[].countintegerrequired
      Contacts and group chats that carry it
    • colorsarray of stringrequired
      Suggested colours
  • 401Missing, invalid or expired credentials
Request
curl "https://api.chatkomodo.com/v1/labels" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/labels', {
  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/labels",
    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/labels', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "labels": [
    {
      "name": "Example",
      "color": "#0a7350",
      "description": "Short description",
      "count": 12
    }
  ],
  "colors": [
    "#0a7350"
  ]
}

Create a label

POST/v1/labels

Adds a label to the catalogue so it can be picked in the inbox and in rules. Names are normalised (lowercase, spaces become dashes); an existing label is updated instead.

Request body

  • namestringrequired
    Up to 60 characters
  • colorstring
    #RRGGBB (default: picked from the name)
  • descriptionstring | null
    Up to 300 characters

Responses

  • 201Created
    Response fields
    • labelobjectrequired
    • label.namestringrequired
      Normalised name: lowercase, spaces become dashes
    • label.colorstringrequired
      #RRGGBB
    • label.descriptionstring | nullrequired
    • label.countintegerrequired
      Contacts and group chats that carry it
  • 400Body is not valid JSON (INVALID_JSON) or fails validation (VALIDATION_FAILED)
  • 401Missing, invalid or expired credentials
  • 403Forbidden for a customer-service agent login (Agent access is not allowed for this operation)
Request
curl -X POST "https://api.chatkomodo.com/v1/labels" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Example",
  "color": "#0a7350",
  "description": "Short description"
}'
const res = await fetch('https://api.chatkomodo.com/v1/labels', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Example",
    "color": "#0a7350",
    "description": "Short description"
  }),
});
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/labels",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "name": "Example",
        "color": "#0a7350",
        "description": "Short description",
    },
    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/labels', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'name' => 'Example',
        'color' => '#0a7350',
        'description' => 'Short description',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 201
{
  "label": {
    "name": "Example",
    "color": "#0a7350",
    "description": "Short description",
    "count": 12
  }
}

Rename or recolour a label

PATCH/v1/labels/{name}

A new name renames the label on every contact, group chat and rule.

Path parameters

  • namestringrequired

Request body

  • namestring
    Up to 60 characters
  • colorstring
  • descriptionstring | null
    Up to 300 characters

Responses

  • 200Saved
    Response fields
    • labelobjectrequired
    • label.namestringrequired
      Normalised name: lowercase, spaces become dashes
    • label.colorstringrequired
      #RRGGBB
    • label.descriptionstring | nullrequired
    • label.countintegerrequired
      Contacts and group chats that carry it
  • 400Body is not valid JSON (INVALID_JSON) or fails validation (VALIDATION_FAILED)
  • 401Missing, invalid or expired credentials
  • 403Forbidden for a customer-service agent login (Agent access is not allowed for this operation)
  • 404Label not found, or it belongs to another tenant (NOT_FOUND)
  • 409A label with the new name already exists (CONFLICT)
Request
curl -X PATCH "https://api.chatkomodo.com/v1/labels/NAME" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Example",
  "color": "#0a7350",
  "description": "Short description"
}'
const res = await fetch('https://api.chatkomodo.com/v1/labels/NAME', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Example",
    "color": "#0a7350",
    "description": "Short description"
  }),
});
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/labels/NAME",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "name": "Example",
        "color": "#0a7350",
        "description": "Short description",
    },
    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/labels/NAME', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'name' => 'Example',
        'color' => '#0a7350',
        'description' => 'Short description',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "label": {
    "name": "Example",
    "color": "#0a7350",
    "description": "Short description",
    "count": 12
  }
}

Delete a label

DEL/v1/labels/{name}

Removes the label from every contact, group chat and rule.

Path parameters

  • namestringrequired

Responses

  • 200Deleted
    Response fields
    • okbooleanrequired
    • removed_fromintegerrequired
  • 401Missing, invalid or expired credentials
  • 403Forbidden for a customer-service agent login (Agent access is not allowed for this operation)
  • 404Label not found, or it belongs to another tenant (NOT_FOUND)
Request
curl -X DELETE "https://api.chatkomodo.com/v1/labels/NAME" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/labels/NAME', {
  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/labels/NAME",
    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/labels/NAME', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

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

List automatic labelling rules

GET/v1/labels/rules

Rules add or remove labels and move people to a lead stage by themselves: keyword (an incoming message contains the words), first_message (a new chat), store_event (an order from the connected store), inactive (no message for N days, checked hourly) and ai (the AI reads the conversation and decides; at most once every 15 minutes per chat, each check uses 1 AI reply of the plan). Rules work on chats with a person. A label someone removed by hand is never put back by a rule.

Responses

  • 200Rules
    Response fields
    • rulesarray of objectrequired
    • rules[].idstringrequired
    • rules[].namestringrequired
    • rules[].enabledbooleanrequired
    • rules[].triggerstringrequired
      One of: keyword, first_message, store_event, inactive, ai
    • rules[].configobjectrequired
    • rules[].config.keywordsarray of string
      keyword: words or phrases, matched at the start of a word (harga also finds harganya), accents and case ignored
    • rules[].config.matchstring
      keyword: any (default) or all keywords
      One of: any, all
    • rules[].config.eventsarray of string
      store_event: which store events
    • rules[].config.daysinteger
      inactive: days without a message (1–365)
    • rules[].config.instructionstring
      ai: when the rule applies, in plain words, e.g. "the customer asks for a price or wants to order"
    • rules[].add_labelsarray of stringrequired
    • rules[].remove_labelsarray of stringrequired
    • rules[].set_stagestring | nullrequired
      Lead stage to move the person to
    • rules[].session_idsarray of string | nullrequired
      Only these devices (null = all)
    • rules[].fire_countintegerrequired
    • rules[].last_fired_atinteger | nullrequired
      Unix time in milliseconds, or null
    • rules[].created_atintegerrequired
      Unix time in milliseconds
    • rules[].updated_atintegerrequired
      Unix time in milliseconds
  • 401Missing, invalid or expired credentials
Request
curl "https://api.chatkomodo.com/v1/labels/rules" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/labels/rules', {
  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/labels/rules",
    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/labels/rules', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "rules": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "enabled": true,
      "trigger": "keyword",
      "config": {
        "keywords": [
          "price"
        ],
        "match": "any",
        "events": [
          "order_created"
        ],
        "days": 1,
        "instruction": "string"
      },
      "add_labels": [
        "string"
      ],
      "remove_labels": [
        "string"
      ],
      "set_stage": "vip",
      "session_ids": [
        "YOUR_DEVICE_ID"
      ],
      "fire_count": 12,
      "last_fired_at": 1767225600000,
      "created_at": 1767225600000,
      "updated_at": 1767225600000
    }
  ]
}

Create a rule

POST/v1/labels/rules

A rule needs labels to add or remove, or a lead stage to set, and the settings of its trigger (see GET /v1/labels/rules). At most 50 rules per business.

Request body

  • namestringrequired
    Up to 80 characters
  • enabledboolean
  • triggerstringrequired
    One of: keyword, first_message, store_event, inactive, ai
  • configobject
  • config.keywordsarray of string
    keyword: words or phrases, matched at the start of a word (harga also finds harganya), accents and case ignored
  • config.matchstring
    keyword: any (default) or all keywords
    One of: any, all
  • config.eventsarray of string
    store_event: which store events
  • config.daysinteger
    inactive: days without a message (1–365)
  • config.instructionstring
    ai: when the rule applies, in plain words, e.g. "the customer asks for a price or wants to order"
  • add_labelsarray of string
  • remove_labelsarray of string
  • set_stagestring | null
  • session_idsarray of string | null

Responses

  • 201Created
    Response fields
    • ruleobjectrequired
    • rule.idstringrequired
    • rule.namestringrequired
    • rule.enabledbooleanrequired
    • rule.triggerstringrequired
      One of: keyword, first_message, store_event, inactive, ai
    • rule.configobjectrequired
    • rule.config.keywordsarray of string
      keyword: words or phrases, matched at the start of a word (harga also finds harganya), accents and case ignored
    • rule.config.matchstring
      keyword: any (default) or all keywords
      One of: any, all
    • rule.config.eventsarray of string
      store_event: which store events
    • rule.config.daysinteger
      inactive: days without a message (1–365)
    • rule.config.instructionstring
      ai: when the rule applies, in plain words, e.g. "the customer asks for a price or wants to order"
    • rule.add_labelsarray of stringrequired
    • rule.remove_labelsarray of stringrequired
    • rule.set_stagestring | nullrequired
      Lead stage to move the person to
    • rule.session_idsarray of string | nullrequired
      Only these devices (null = all)
    • rule.fire_countintegerrequired
    • rule.last_fired_atinteger | nullrequired
      Unix time in milliseconds, or null
    • rule.created_atintegerrequired
      Unix time in milliseconds
    • rule.updated_atintegerrequired
      Unix time in milliseconds
  • 400Invalid rule (VALIDATION_FAILED) or more than 50 rules (LIMIT_REACHED)
  • 401Missing, invalid or expired credentials
  • 403Forbidden for a customer-service agent login (Agent access is not allowed for this operation)
Request
curl -X POST "https://api.chatkomodo.com/v1/labels/rules" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Example",
  "enabled": true,
  "trigger": "keyword",
  "config": {
    "keywords": [
      "price"
    ],
    "match": "any",
    "events": [
      "order_created"
    ],
    "days": 1
  }
}'
const res = await fetch('https://api.chatkomodo.com/v1/labels/rules', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Example",
    "enabled": true,
    "trigger": "keyword",
    "config": {
      "keywords": [
        "price"
      ],
      "match": "any",
      "events": [
        "order_created"
      ],
      "days": 1
    }
  }),
});
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/labels/rules",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "name": "Example",
        "enabled": True,
        "trigger": "keyword",
        "config": {
            "keywords": [
                "price",
            ],
            "match": "any",
            "events": [
                "order_created",
            ],
            "days": 1,
        },
    },
    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/labels/rules', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'name' => 'Example',
        'enabled' => true,
        'trigger' => 'keyword',
        'config' => [
            'keywords' => [
                'price',
            ],
            'match' => 'any',
            'events' => [
                'order_created',
            ],
            'days' => 1,
        ],
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 201
{
  "rule": {
    "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
    "name": "Example",
    "enabled": true,
    "trigger": "keyword",
    "config": {
      "keywords": [
        "price"
      ],
      "match": "any",
      "events": [
        "order_created"
      ],
      "days": 1,
      "instruction": "string"
    },
    "add_labels": [
      "string"
    ],
    "remove_labels": [
      "string"
    ],
    "set_stage": "vip",
    "session_ids": [
      "YOUR_DEVICE_ID"
    ],
    "fire_count": 12,
    "last_fired_at": 1767225600000,
    "created_at": 1767225600000,
    "updated_at": 1767225600000
  }
}

Change a rule

PATCH/v1/labels/rules/{id}

Fields left out keep their value. enabled: false pauses the rule.

Path parameters

  • idstringrequired
    Rule id

Request body

  • namestring
    Up to 80 characters
  • enabledboolean
  • triggerstring
    One of: keyword, first_message, store_event, inactive, ai
  • configobject
  • config.keywordsarray of string
    keyword: words or phrases, matched at the start of a word (harga also finds harganya), accents and case ignored
  • config.matchstring
    keyword: any (default) or all keywords
    One of: any, all
  • config.eventsarray of string
    store_event: which store events
  • config.daysinteger
    inactive: days without a message (1–365)
  • config.instructionstring
    ai: when the rule applies, in plain words, e.g. "the customer asks for a price or wants to order"
  • add_labelsarray of string
  • remove_labelsarray of string
  • set_stagestring | null
  • session_idsarray of string | null

Responses

  • 200Saved
    Response fields
    • ruleobjectrequired
    • rule.idstringrequired
    • rule.namestringrequired
    • rule.enabledbooleanrequired
    • rule.triggerstringrequired
      One of: keyword, first_message, store_event, inactive, ai
    • rule.configobjectrequired
    • rule.config.keywordsarray of string
      keyword: words or phrases, matched at the start of a word (harga also finds harganya), accents and case ignored
    • rule.config.matchstring
      keyword: any (default) or all keywords
      One of: any, all
    • rule.config.eventsarray of string
      store_event: which store events
    • rule.config.daysinteger
      inactive: days without a message (1–365)
    • rule.config.instructionstring
      ai: when the rule applies, in plain words, e.g. "the customer asks for a price or wants to order"
    • rule.add_labelsarray of stringrequired
    • rule.remove_labelsarray of stringrequired
    • rule.set_stagestring | nullrequired
      Lead stage to move the person to
    • rule.session_idsarray of string | nullrequired
      Only these devices (null = all)
    • rule.fire_countintegerrequired
    • rule.last_fired_atinteger | nullrequired
      Unix time in milliseconds, or null
    • rule.created_atintegerrequired
      Unix time in milliseconds
    • rule.updated_atintegerrequired
      Unix time in milliseconds
  • 400Body is not valid JSON (INVALID_JSON) or fails validation (VALIDATION_FAILED)
  • 401Missing, invalid or expired credentials
  • 403Forbidden for a customer-service agent login (Agent access is not allowed for this operation)
  • 404Rule not found, or it belongs to another tenant (NOT_FOUND)
Request
curl -X PATCH "https://api.chatkomodo.com/v1/labels/rules/ID" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Example",
  "enabled": true,
  "trigger": "keyword",
  "config": {
    "keywords": [
      "price"
    ],
    "match": "any",
    "events": [
      "order_created"
    ],
    "days": 1
  }
}'
const res = await fetch('https://api.chatkomodo.com/v1/labels/rules/ID', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Example",
    "enabled": true,
    "trigger": "keyword",
    "config": {
      "keywords": [
        "price"
      ],
      "match": "any",
      "events": [
        "order_created"
      ],
      "days": 1
    }
  }),
});
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/labels/rules/ID",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "name": "Example",
        "enabled": True,
        "trigger": "keyword",
        "config": {
            "keywords": [
                "price",
            ],
            "match": "any",
            "events": [
                "order_created",
            ],
            "days": 1,
        },
    },
    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/labels/rules/ID', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'name' => 'Example',
        'enabled' => true,
        'trigger' => 'keyword',
        'config' => [
            'keywords' => [
                'price',
            ],
            'match' => 'any',
            'events' => [
                'order_created',
            ],
            'days' => 1,
        ],
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "rule": {
    "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
    "name": "Example",
    "enabled": true,
    "trigger": "keyword",
    "config": {
      "keywords": [
        "price"
      ],
      "match": "any",
      "events": [
        "order_created"
      ],
      "days": 1,
      "instruction": "string"
    },
    "add_labels": [
      "string"
    ],
    "remove_labels": [
      "string"
    ],
    "set_stage": "vip",
    "session_ids": [
      "YOUR_DEVICE_ID"
    ],
    "fire_count": 12,
    "last_fired_at": 1767225600000,
    "created_at": 1767225600000,
    "updated_at": 1767225600000
  }
}

Delete a rule

DEL/v1/labels/rules/{id}

Labels the rule already applied stay where they are.

Path parameters

  • idstringrequired
    Rule 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)
  • 404Rule not found, or it belongs to another tenant (NOT_FOUND)
Request
curl -X DELETE "https://api.chatkomodo.com/v1/labels/rules/ID" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/labels/rules/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/labels/rules/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/labels/rules/ID', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

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

The leads board

GET/v1/leads

People in a lead stage (at most 1 000, most recently moved first) with their labels and latest chat, and the business's stages. Filter by label or search q (name or number).

Query parameters

  • labelstring
  • qstring

Responses

  • 200Board
    Response fields
    • stagesarray of objectrequired
    • stages[].idstringrequired
    • stages[].namestringrequired
    • stages[].colorstringrequired
    • leadsarray of objectrequired
    • leads[].contact_idstringrequired
    • leads[].namestring | nullrequired
    • leads[].phonestringrequired
    • leads[].stagestringrequired
    • leads[].labelsarray of objectrequired
    • leads[].labels[].namestringrequired
    • leads[].labels[].colorstringrequired
    • leads[].stage_updated_atinteger | nullrequired
      Unix time in milliseconds, or null
    • leads[].session_idstring | nullrequired
    • leads[].chat_idstring | nullrequired
    • leads[].last_messagestring | nullrequired
    • leads[].last_message_atinteger | nullrequired
      Unix time in milliseconds, or null
    • leads[].unread_countintegerrequired
  • 401Missing, invalid or expired credentials
Request
curl "https://api.chatkomodo.com/v1/leads" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/leads', {
  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/leads",
    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/leads', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "stages": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "color": "#0a7350"
    }
  ],
  "leads": [
    {
      "contact_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "phone": "+447700900123",
      "stage": "vip",
      "labels": [
        {
          "name": "Example",
          "color": "#0a7350"
        }
      ],
      "stage_updated_at": 1767225600000,
      "session_id": "YOUR_DEVICE_ID",
      "chat_id": "+447700900123",
      "last_message": "Hi Sam, your order has shipped.",
      "last_message_at": 1767225600000,
      "unread_count": 12
    }
  ]
}

Set the lead stages

PUT/v1/leads/stages

1–12 stages in board order. People in a stage that is removed move to the first stage; rules that set it stop setting a stage.

Request body

  • stagesarray of objectrequired
  • stages[].idstring
    Kept when renaming; default: from the name
  • stages[].namestringrequired
  • stages[].colorstring

Responses

  • 200Saved
    Response fields
    • stagesarray of objectrequired
    • stages[].idstringrequired
    • stages[].namestringrequired
    • stages[].colorstringrequired
  • 400Body is not valid JSON (INVALID_JSON) or fails validation (VALIDATION_FAILED)
  • 401Missing, invalid or expired credentials
  • 403Forbidden for a customer-service agent login (Agent access is not allowed for this operation)
Request
curl -X PUT "https://api.chatkomodo.com/v1/leads/stages" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "stages": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "color": "#0a7350"
    }
  ]
}'
const res = await fetch('https://api.chatkomodo.com/v1/leads/stages', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "stages": [
      {
        "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
        "name": "Example",
        "color": "#0a7350"
      }
    ]
  }),
});
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/leads/stages",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "stages": [
            {
                "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
                "name": "Example",
                "color": "#0a7350",
            },
        ],
    },
    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/leads/stages', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'stages' => [
            [
                'id' => '01JA5V6Q8M2H7G4K9P3T0R1S2X',
                'name' => 'Example',
                'color' => '#0a7350',
            ],
        ],
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "stages": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "color": "#0a7350"
    }
  ]
}

Move a lead

PATCH/v1/leads/{contactId}

stage: null takes the person off the board.

Path parameters

  • contactIdstringrequired

Request body

  • stagestring | nullrequired

Responses

  • 200Moved
    Response fields
    • okbooleanrequired
    • stagestring | nullrequired
  • 400Unknown lead stage (VALIDATION_FAILED)
  • 401Missing, invalid or expired credentials
  • 404Contact not found, or it belongs to another tenant (NOT_FOUND)
Request
curl -X PATCH "https://api.chatkomodo.com/v1/leads/CONTACT_ID" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "stage": "vip"
}'
const res = await fetch('https://api.chatkomodo.com/v1/leads/CONTACT_ID', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "stage": "vip"
  }),
});
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/leads/CONTACT_ID",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "stage": "vip",
    },
    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/leads/CONTACT_ID', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'stage' => 'vip',
    ],
]);

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

Labels and stage of a chat

GET/v1/sessions/{sid}/inbox/{chatId}/labels

A chat can carry several labels (up to 20). For a chat with a person they are that person's contact labels, shared across devices.

Path parameters

  • sidstringrequired
    Session (device) id
  • chatIdstringrequired
    Chat id (JID), URL-encoded

Responses

  • 200Labels
    Response fields
    • labelsarray of objectrequired
    • labels[].namestringrequired
    • labels[].colorstringrequired
    • stagestring | nullrequired
      Lead stage (chats with a person only)
    • stagesarray of objectrequired
    • stages[].idstringrequired
    • stages[].namestringrequired
    • stages[].colorstringrequired
    • personbooleanrequired
      The chat is with a person (labels live on their contact)
    • historyarray of objectrequired
      Latest 20 changes
    • history[].labelstringrequired
    • history[].actionstringrequired
      One of: add, remove, stage
    • history[].sourcestringrequired
      One of: manual, rule, ai, store
    • history[].rule_idstring | nullrequired
    • history[].rule_namestring | nullrequired
    • history[].actorstring | nullrequired
    • history[].atintegerrequired
      Unix time in milliseconds
  • 401Missing, invalid or expired credentials
Request
curl "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/inbox/447700900123%40s.whatsapp.net/labels" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/inbox/447700900123%40s.whatsapp.net/labels', {
  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/inbox/447700900123%40s.whatsapp.net/labels",
    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/inbox/447700900123%40s.whatsapp.net/labels', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "labels": [
    {
      "name": "Example",
      "color": "#0a7350"
    }
  ],
  "stage": "vip",
  "stages": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "color": "#0a7350"
    }
  ],
  "person": true,
  "history": [
    {
      "label": "string",
      "action": "add",
      "source": "manual",
      "rule_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "rule_name": "string",
      "actor": "string",
      "at": 1767225600000
    }
  ]
}

Add or remove labels on a chat

POST/v1/sessions/{sid}/inbox/{chatId}/labels

Adds and removes some labels, keeping the others. Adding a label by hand lifts an earlier manual removal, so rules may add it again later.

Path parameters

  • sidstringrequired
    Session (device) id
  • chatIdstringrequired
    Chat id (JID), URL-encoded

Request body

  • addarray of string
  • removearray of string

Responses

  • 200Saved
    Response fields
    • labelsarray of objectrequired
    • labels[].namestringrequired
    • labels[].colorstringrequired
    • stagestring | nullrequired
      Lead stage (chats with a person only)
    • stagesarray of objectrequired
    • stages[].idstringrequired
    • stages[].namestringrequired
    • stages[].colorstringrequired
    • personbooleanrequired
      The chat is with a person (labels live on their contact)
    • historyarray of objectrequired
      Latest 20 changes
    • history[].labelstringrequired
    • history[].actionstringrequired
      One of: add, remove, stage
    • history[].sourcestringrequired
      One of: manual, rule, ai, store
    • history[].rule_idstring | nullrequired
    • history[].rule_namestring | nullrequired
    • history[].actorstring | nullrequired
    • history[].atintegerrequired
      Unix time in milliseconds
  • 400Invalid body or more than 20 labels (LIMIT_REACHED)
  • 401Missing, invalid or expired credentials
Request
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/inbox/447700900123%40s.whatsapp.net/labels" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "add": [
    "string"
  ],
  "remove": [
    "string"
  ]
}'
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/inbox/447700900123%40s.whatsapp.net/labels', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "add": [
      "string"
    ],
    "remove": [
      "string"
    ]
  }),
});
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/inbox/447700900123%40s.whatsapp.net/labels",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "add": [
            "string",
        ],
        "remove": [
            "string",
        ],
    },
    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/inbox/447700900123%40s.whatsapp.net/labels', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'add' => [
            'string',
        ],
        'remove' => [
            'string',
        ],
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "labels": [
    {
      "name": "Example",
      "color": "#0a7350"
    }
  ],
  "stage": "vip",
  "stages": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "color": "#0a7350"
    }
  ],
  "person": true,
  "history": [
    {
      "label": "string",
      "action": "add",
      "source": "manual",
      "rule_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "rule_name": "string",
      "actor": "string",
      "at": 1767225600000
    }
  ]
}

Replace the labels of a chat

PUT/v1/sessions/{sid}/inbox/{chatId}/labels

Labelling a person who is not a contact yet saves them as a contact. Removing a label by hand stops rules and the AI from adding it again to this person.

Path parameters

  • sidstringrequired
    Session (device) id
  • chatIdstringrequired
    Chat id (JID), URL-encoded

Request body

  • labelsarray of stringrequired
    Up to 20 items

Responses

  • 200Saved
    Response fields
    • labelsarray of objectrequired
    • labels[].namestringrequired
    • labels[].colorstringrequired
    • stagestring | nullrequired
      Lead stage (chats with a person only)
    • stagesarray of objectrequired
    • stages[].idstringrequired
    • stages[].namestringrequired
    • stages[].colorstringrequired
    • personbooleanrequired
      The chat is with a person (labels live on their contact)
    • historyarray of objectrequired
      Latest 20 changes
    • history[].labelstringrequired
    • history[].actionstringrequired
      One of: add, remove, stage
    • history[].sourcestringrequired
      One of: manual, rule, ai, store
    • history[].rule_idstring | nullrequired
    • history[].rule_namestring | nullrequired
    • history[].actorstring | nullrequired
    • history[].atintegerrequired
      Unix time in milliseconds
  • 400Invalid body or more than 20 labels (LIMIT_REACHED)
  • 401Missing, invalid or expired credentials
Request
curl -X PUT "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/inbox/447700900123%40s.whatsapp.net/labels" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "labels": [
    "string"
  ]
}'
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/inbox/447700900123%40s.whatsapp.net/labels', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "labels": [
      "string"
    ]
  }),
});
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/inbox/447700900123%40s.whatsapp.net/labels",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "labels": [
            "string",
        ],
    },
    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/inbox/447700900123%40s.whatsapp.net/labels', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'labels' => [
            'string',
        ],
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "labels": [
    {
      "name": "Example",
      "color": "#0a7350"
    }
  ],
  "stage": "vip",
  "stages": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "color": "#0a7350"
    }
  ],
  "person": true,
  "history": [
    {
      "label": "string",
      "action": "add",
      "source": "manual",
      "rule_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "rule_name": "string",
      "actor": "string",
      "at": 1767225600000
    }
  ]
}

Set the lead stage of a chat

PUT/v1/sessions/{sid}/inbox/{chatId}/stage

Puts the person on the leads board in this stage (null takes them off). The person is saved as a contact when needed.

Path parameters

  • sidstringrequired
    Session (device) id
  • chatIdstringrequired
    Chat id (JID), URL-encoded

Request body

  • stagestring | nullrequired

Responses

  • 200Saved
    Response fields
    • labelsarray of objectrequired
    • labels[].namestringrequired
    • labels[].colorstringrequired
    • stagestring | nullrequired
      Lead stage (chats with a person only)
    • stagesarray of objectrequired
    • stages[].idstringrequired
    • stages[].namestringrequired
    • stages[].colorstringrequired
    • personbooleanrequired
      The chat is with a person (labels live on their contact)
    • historyarray of objectrequired
      Latest 20 changes
    • history[].labelstringrequired
    • history[].actionstringrequired
      One of: add, remove, stage
    • history[].sourcestringrequired
      One of: manual, rule, ai, store
    • history[].rule_idstring | nullrequired
    • history[].rule_namestring | nullrequired
    • history[].actorstring | nullrequired
    • history[].atintegerrequired
      Unix time in milliseconds
  • 400Not a chat with a person, or an unknown stage (VALIDATION_FAILED)
  • 401Missing, invalid or expired credentials
Request
curl -X PUT "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/inbox/447700900123%40s.whatsapp.net/stage" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "stage": "vip"
}'
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/inbox/447700900123%40s.whatsapp.net/stage', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "stage": "vip"
  }),
});
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/inbox/447700900123%40s.whatsapp.net/stage",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "stage": "vip",
    },
    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/inbox/447700900123%40s.whatsapp.net/stage', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'stage' => 'vip',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "labels": [
    {
      "name": "Example",
      "color": "#0a7350"
    }
  ],
  "stage": "vip",
  "stages": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "color": "#0a7350"
    }
  ],
  "person": true,
  "history": [
    {
      "label": "string",
      "action": "add",
      "source": "manual",
      "rule_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "rule_name": "string",
      "actor": "string",
      "at": 1767225600000
    }
  ]
}

Run the AI rules on a chat now

POST/v1/sessions/{sid}/inbox/{chatId}/labels/ai

Lets the AI rules check this conversation immediately (normally they run after incoming messages, at most every 15 minutes per chat). Uses 1 AI reply of the plan; nothing happens without AI rules, an AI provider or AI quota.

Path parameters

  • sidstringrequired
    Session (device) id
  • chatIdstringrequired
    Chat id (JID), URL-encoded

Responses

  • 200Checked
    Response fields
    • labelsarray of objectrequired
    • labels[].namestringrequired
    • labels[].colorstringrequired
    • stagestring | nullrequired
      Lead stage (chats with a person only)
    • stagesarray of objectrequired
    • stages[].idstringrequired
    • stages[].namestringrequired
    • stages[].colorstringrequired
    • personbooleanrequired
      The chat is with a person (labels live on their contact)
    • historyarray of objectrequired
      Latest 20 changes
    • history[].labelstringrequired
    • history[].actionstringrequired
      One of: add, remove, stage
    • history[].sourcestringrequired
      One of: manual, rule, ai, store
    • history[].rule_idstring | nullrequired
    • history[].rule_namestring | nullrequired
    • history[].actorstring | nullrequired
    • history[].atintegerrequired
      Unix time in milliseconds
    • firedarray of stringrequired
      Ids of the rules that applied
  • 400Not a chat with a person (VALIDATION_FAILED)
  • 401Missing, invalid or expired credentials
  • 403Forbidden for a customer-service agent login (Agent access is not allowed for this operation)
Request
curl -X POST "https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/inbox/447700900123%40s.whatsapp.net/labels/ai" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/sessions/YOUR_DEVICE_ID/inbox/447700900123%40s.whatsapp.net/labels/ai', {
  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/inbox/447700900123%40s.whatsapp.net/labels/ai",
    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/inbox/447700900123%40s.whatsapp.net/labels/ai', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "labels": [
    {
      "name": "Example",
      "color": "#0a7350"
    }
  ],
  "stage": "vip",
  "stages": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "color": "#0a7350"
    }
  ],
  "person": true,
  "history": [
    {
      "label": "string",
      "action": "add",
      "source": "manual",
      "rule_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "rule_name": "string",
      "actor": "string",
      "at": 1767225600000
    }
  ],
  "fired": [
    "string"
  ]
}