ChatKomodo Docs Home Sign in

API reference · Conversations

AI assistant

AI replies from the business information, knowledge documents and web pages, saved answers, questions the assistant could not answer, the AI report, and the inbox helpers (suggested reply, chat summary). Changing settings, documents and saved answers needs the owner's dashboard login.

Get AI reply settings and quota

GET/v1/ai-reply

AI auto-reply settings and this month's quota. AI answers a 1:1 message only when no auto-reply rule or chatbot flow did, and steps back in a chat for pause_hours after a person from the business sends a message from the dashboard.

Responses

  • 200State
    Response fields
    • settingsobjectrequired
    • settings.enabledbooleanrequired
    • settings.instructionsstringrequired
      Persona, tone, dos and don'ts
      Up to 2 000 characters
    • settings.knowledgestringrequired
      Facts the assistant may use: products, prices, hours, policies, FAQ
      Up to 12 000 characters
    • settings.handoff_keywordsarray of stringrequired
      A message containing one of these words pauses AI for the chat (lower-case, whole words)
    • settings.handoff_messagestringrequired
      Sent when a customer asks for a person (empty = say nothing)
    • settings.pause_hoursintegerrequired
      How long AI stays quiet in a chat after a person from the business replied or a handoff
      From 0 to 168
    • settings.max_per_chat_per_dayintegerrequired
      From 1 to 200
    • quotaobjectrequired
    • quota.limitintegerrequired
      AI replies included in the plan per month (0 = not included)
    • quota.usedintegerrequired
    • quota.periodstringrequired
      YYYY-MM
    • availablebooleanrequired
      Whether the platform has an AI model set up
Request
curl "https://api.chatkomodo.com/v1/ai-reply" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply', {
  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/ai-reply",
    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/ai-reply', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "settings": {
    "enabled": true,
    "instructions": "string",
    "knowledge": "string",
    "handoff_keywords": [
      "string"
    ],
    "handoff_message": "Hi Sam, your order has shipped.",
    "pause_hours": 1,
    "max_per_chat_per_day": 1
  },
  "quota": {
    "limit": 50,
    "used": 12,
    "period": "string"
  },
  "available": true
}

Update AI reply settings

PUT/v1/ai-reply

Change AI auto-reply settings (any subset). Owner login only. Turning it on needs some knowledge, instructions or a ready knowledge document. Audited as ai_reply.update.

Request body

  • enabledboolean
  • instructionsstring
    Persona, tone, dos and don'ts
    Up to 2 000 characters
  • knowledgestring
    Facts the assistant may use: products, prices, hours, policies, FAQ
    Up to 12 000 characters
  • handoff_keywordsarray of string
    A message containing one of these words pauses AI for the chat (lower-case, whole words)
  • handoff_messagestring
    Sent when a customer asks for a person (empty = say nothing)
  • pause_hoursinteger
    How long AI stays quiet in a chat after a person from the business replied or a handoff
    From 0 to 168
  • max_per_chat_per_dayinteger
    From 1 to 200

Responses

  • 200Saved
    Response fields
    • settingsobjectrequired
    • settings.enabledbooleanrequired
    • settings.instructionsstringrequired
      Persona, tone, dos and don'ts
      Up to 2 000 characters
    • settings.knowledgestringrequired
      Facts the assistant may use: products, prices, hours, policies, FAQ
      Up to 12 000 characters
    • settings.handoff_keywordsarray of stringrequired
      A message containing one of these words pauses AI for the chat (lower-case, whole words)
    • settings.handoff_messagestringrequired
      Sent when a customer asks for a person (empty = say nothing)
    • settings.pause_hoursintegerrequired
      How long AI stays quiet in a chat after a person from the business replied or a handoff
      From 0 to 168
    • settings.max_per_chat_per_dayintegerrequired
      From 1 to 200
    • quotaobjectrequired
    • quota.limitintegerrequired
      AI replies included in the plan per month (0 = not included)
    • quota.usedintegerrequired
    • quota.periodstringrequired
      YYYY-MM
    • availablebooleanrequired
      Whether the platform has an AI model set up
  • 400VALIDATION_FAILED
  • 403Not the owner
Request
curl -X PUT "https://api.chatkomodo.com/v1/ai-reply" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "enabled": true,
  "knowledge": "Acme Dental, Jl. Sudirman 12, Jakarta. Open Mon–Sat 9:00–17:00. Check-up Rp150.000. We accept BPJS and QRIS.",
  "instructions": "Friendly and short. Call customers \"Kak\". Never promise discounts.",
  "handoff_keywords": [
    "admin",
    "human",
    "complaint"
  ]
}'
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "enabled": true,
    "knowledge": "Acme Dental, Jl. Sudirman 12, Jakarta. Open Mon–Sat 9:00–17:00. Check-up Rp150.000. We accept BPJS and QRIS.",
    "instructions": "Friendly and short. Call customers \"Kak\". Never promise discounts.",
    "handoff_keywords": [
      "admin",
      "human",
      "complaint"
    ]
  }),
});
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/ai-reply",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "enabled": True,
        "knowledge": "Acme Dental, Jl. Sudirman 12, Jakarta. Open Mon–Sat 9:00–17:00. Check-up Rp150.000. We accept BPJS and QRIS.",
        "instructions": "Friendly and short. Call customers \"Kak\". Never promise discounts.",
        "handoff_keywords": [
            "admin",
            "human",
            "complaint",
        ],
    },
    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/ai-reply', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'enabled' => true,
        'knowledge' => 'Acme Dental, Jl. Sudirman 12, Jakarta. Open Mon–Sat 9:00–17:00. Check-up Rp150.000. We accept BPJS and QRIS.',
        'instructions' => 'Friendly and short. Call customers "Kak". Never promise discounts.',
        'handoff_keywords' => [
            'admin',
            'human',
            'complaint',
        ],
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "settings": {
    "enabled": true,
    "instructions": "string",
    "knowledge": "string",
    "handoff_keywords": [
      "string"
    ],
    "handoff_message": "Hi Sam, your order has shipped.",
    "pause_hours": 1,
    "max_per_chat_per_day": 1
  },
  "quota": {
    "limit": 50,
    "used": 12,
    "period": "string"
  },
  "available": true
}

Try a question on the assistant

POST/v1/ai-reply/preview

Ask the assistant one question with the saved settings, or with draft settings before saving. Counts as one AI reply. Owner only, 10 per minute.

Request body

  • messagestringrequired
    1–2 000 characters
  • settingsobject
  • settings.enabledboolean
  • settings.instructionsstring
    Persona, tone, dos and don'ts
    Up to 2 000 characters
  • settings.knowledgestring
    Facts the assistant may use: products, prices, hours, policies, FAQ
    Up to 12 000 characters
  • settings.handoff_keywordsarray of string
    A message containing one of these words pauses AI for the chat (lower-case, whole words)
  • settings.handoff_messagestring
    Sent when a customer asks for a person (empty = say nothing)
  • settings.pause_hoursinteger
    How long AI stays quiet in a chat after a person from the business replied or a handoff
    From 0 to 168
  • settings.max_per_chat_per_dayinteger
    From 1 to 200

Responses

  • 200Answer
    Response fields
    • replystringrequired
    • msintegerrequired
    • sourcesarray of stringrequired
      Names of the knowledge documents whose passages were given to the model; :faq stands for the business's saved answers
    • unsurebooleanrequired
      The model said the information does not answer the question (a customer would get this reply and the question would be listed as unanswered)
    • quotaobjectrequired
    • quota.limitintegerrequired
      AI replies included in the plan per month (0 = not included)
    • quota.usedintegerrequired
    • quota.periodstringrequired
      YYYY-MM
  • 402No AI replies left this month (LIMIT_REACHED)
  • 429More than 10 previews a minute
  • 502The model did not answer (UPSTREAM_ERROR)
  • 503The platform has no AI model set up (NOT_CONFIGURED)
Request
curl -X POST "https://api.chatkomodo.com/v1/ai-reply/preview" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "message": "What time do you open on Saturday?"
}'
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/preview', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "message": "What time do you open on Saturday?"
  }),
});
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/ai-reply/preview",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "message": "What time do you open on Saturday?",
    },
    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/ai-reply/preview', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'message' => 'What time do you open on Saturday?',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "reply": "Hi Sam, your order has shipped.",
  "ms": 1,
  "sources": [
    "string"
  ],
  "unsure": true,
  "quota": {
    "limit": 50,
    "used": 12,
    "period": "string"
  }
}

Pause AI replies in a chat

POST/v1/ai-reply/chats/pause

Stop AI replies in one chat for hours (1–720; default the tenant's pause_hours). Allowed for support agents.

Request body

  • session_idstringrequired
  • chat_idstringrequired
  • hoursinteger
    From 1 to 720

Responses

  • 200Done
    Response fields
    • okbooleanrequired
  • 400Unknown session or missing chat_id
Request
curl -X POST "https://api.chatkomodo.com/v1/ai-reply/chats/pause" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "session_id": "YOUR_DEVICE_ID",
  "chat_id": "[email protected]",
  "hours": 12
}'
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/chats/pause', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "session_id": "YOUR_DEVICE_ID",
    "chat_id": "[email protected]",
    "hours": 12
  }),
});
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/ai-reply/chats/pause",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "session_id": "YOUR_DEVICE_ID",
        "chat_id": "[email protected]",
        "hours": 12,
    },
    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/ai-reply/chats/pause', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'session_id' => 'YOUR_DEVICE_ID',
        'chat_id' => '[email protected]',
        'hours' => 12,
    ],
]);

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

Resume AI replies in a chat

POST/v1/ai-reply/chats/resume

Let AI reply in this chat again.

Request body

  • session_idstringrequired
  • chat_idstringrequired

Responses

  • 200Done
    Response fields
    • okbooleanrequired
  • 400Unknown session or missing chat_id
Request
curl -X POST "https://api.chatkomodo.com/v1/ai-reply/chats/resume" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "session_id": "YOUR_DEVICE_ID",
  "chat_id": "[email protected]"
}'
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/chats/resume', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "session_id": "YOUR_DEVICE_ID",
    "chat_id": "[email protected]"
  }),
});
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/ai-reply/chats/resume",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "session_id": "YOUR_DEVICE_ID",
        "chat_id": "[email protected]",
    },
    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/ai-reply/chats/resume', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'session_id' => 'YOUR_DEVICE_ID',
        'chat_id' => '[email protected]',
    ],
]);

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

List knowledge documents

GET/v1/ai-reply/documents

Knowledge documents for AI replies: files the business uploaded (price lists, menus, policies, FAQs), their reading status, and the plan's limits. When a customer writes, the passages that best match the message are given to the AI with the typed knowledge — the AI answers from them and says a person will follow up when they do not cover the question.

Responses

  • 200State
    Response fields
    • availablebooleanrequired
      Whether the platform offers knowledge documents (switched off by the operator = false)
    • limitsobjectrequired
    • limits.filesintegerrequired
      Documents allowed by the plan (0 = not included)
    • limits.mbintegerrequired
    • limits.file_mbnumberrequired
      Largest single file
    • limits.typesarray of stringrequired
    • usedobjectrequired
    • used.filesintegerrequired
    • used.mbnumberrequired
    • documentsarray of objectrequired
    • documents[].idstringrequired
    • documents[].namestringrequired
      File name as uploaded
    • documents[].extstringrequired
      pdf, docx, xlsx, csv, txt, md or html
    • documents[].sizeintegerrequired
      Bytes
    • documents[].statusstringrequired
      parsing while the engine reads the file (seconds to a few minutes), ready when AI replies use it, failed with error (e.g. a scanned PDF without text), deleting while the engine copy is removed
      One of: uploading, parsing, ready, failed, deleting
    • documents[].progressnumberrequired
      0–1 while parsing
    • documents[].chunksintegerrequired
      Passages the file was cut into
    • documents[].errorstring | nullrequired
    • documents[].enginestringrequired
      Who read it: ragflow (the knowledge engine) or local (the backup reader on the API server, used when the engine is not set up, unreachable or failed the file)
      One of: ragflow, local
    • documents[].source_urlstring | nullrequired
      The web page a document was read from (POST /v1/ai-reply/documents/url); name is then the page title
    • documents[].created_atintegerrequired
    • documents[].updated_atintegerrequired
Request
curl "https://api.chatkomodo.com/v1/ai-reply/documents" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/documents', {
  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/ai-reply/documents",
    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/ai-reply/documents', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "available": true,
  "limits": {
    "files": 12,
    "mb": 1,
    "file_mb": 0.5,
    "types": [
      "string"
    ]
  },
  "used": {
    "files": 12,
    "mb": 0.5
  },
  "documents": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "ext": "string",
      "size": 12,
      "status": "uploading",
      "progress": 0.5,
      "chunks": 12,
      "error": "string",
      "engine": "ragflow",
      "source_url": "https://example.com/file.jpg",
      "created_at": 1767225600000,
      "updated_at": 1767225600000
    }
  ]
}

Upload a knowledge document

POST/v1/ai-reply/documents

Upload one file (multipart field file): PDF with selectable text, Word (.docx), Excel (.xlsx), CSV, text, Markdown or HTML, up to 8 MB. The knowledge engine reads it in the background (status parsing → ready); when the engine is not set up, unreachable or fails the file, the backup reader on the API server reads it instead (engine: local, usually ready at once). Owner login only; 30 uploads (files and links) per 10 minutes. Audited as ai_kb.upload.

Request body (multipart/form-data)

  • filestringrequired
    The file (binary)

Responses

  • 201Accepted
    Response fields
    • availablebooleanrequired
      Whether the platform offers knowledge documents (switched off by the operator = false)
    • limitsobjectrequired
    • limits.filesintegerrequired
      Documents allowed by the plan (0 = not included)
    • limits.mbintegerrequired
    • limits.file_mbnumberrequired
      Largest single file
    • limits.typesarray of stringrequired
    • usedobjectrequired
    • used.filesintegerrequired
    • used.mbnumberrequired
    • documentsarray of objectrequired
    • documents[].idstringrequired
    • documents[].namestringrequired
      File name as uploaded
    • documents[].extstringrequired
      pdf, docx, xlsx, csv, txt, md or html
    • documents[].sizeintegerrequired
      Bytes
    • documents[].statusstringrequired
      parsing while the engine reads the file (seconds to a few minutes), ready when AI replies use it, failed with error (e.g. a scanned PDF without text), deleting while the engine copy is removed
      One of: uploading, parsing, ready, failed, deleting
    • documents[].progressnumberrequired
      0–1 while parsing
    • documents[].chunksintegerrequired
      Passages the file was cut into
    • documents[].errorstring | nullrequired
    • documents[].enginestringrequired
      Who read it: ragflow (the knowledge engine) or local (the backup reader on the API server, used when the engine is not set up, unreachable or failed the file)
      One of: ragflow, local
    • documents[].source_urlstring | nullrequired
      The web page a document was read from (POST /v1/ai-reply/documents/url); name is then the page title
    • documents[].created_atintegerrequired
    • documents[].updated_atintegerrequired
    • documentobjectrequired
    • document.idstringrequired
    • document.namestringrequired
      File name as uploaded
    • document.extstringrequired
      pdf, docx, xlsx, csv, txt, md or html
    • document.sizeintegerrequired
      Bytes
    • document.statusstringrequired
      parsing while the engine reads the file (seconds to a few minutes), ready when AI replies use it, failed with error (e.g. a scanned PDF without text), deleting while the engine copy is removed
      One of: uploading, parsing, ready, failed, deleting
    • document.progressnumberrequired
      0–1 while parsing
    • document.chunksintegerrequired
      Passages the file was cut into
    • document.errorstring | nullrequired
    • document.enginestringrequired
      Who read it: ragflow (the knowledge engine) or local (the backup reader on the API server, used when the engine is not set up, unreachable or failed the file)
      One of: ragflow, local
    • document.source_urlstring | nullrequired
      The web page a document was read from (POST /v1/ai-reply/documents/url); name is then the page title
    • document.created_atintegerrequired
    • document.updated_atintegerrequired
  • 400VALIDATION_FAILED, EMPTY_FILE, BAD_FILE, BAD_ENCODING
  • 402NOT_IN_PLAN or LIMIT_REACHED (files or MB of the plan)
  • 403Not the owner
  • 413FILE_TOO_LARGE
  • 415UNSUPPORTED_TYPE
  • 429Too many uploads
  • 502ENGINE_ERROR — the knowledge engine did not accept the request; try again
  • 503NOT_CONFIGURED or PLATFORM_FULL
Request
curl -X POST "https://api.chatkomodo.com/v1/ai-reply/documents" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -F "[email protected]"
import { readFile } from 'node:fs/promises';

const form = new FormData();
form.append('file', new Blob([await readFile('price-list.pdf')]), 'price-list.pdf');

const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/documents', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
  },
  body: form,
});
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/ai-reply/documents",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    files={"file": open("price-list.pdf", "rb")},
    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/ai-reply/documents', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'multipart' => [
        ['name' => 'file', 'contents' => fopen('price-list.pdf', 'r')],
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 201
{
  "available": true,
  "limits": {
    "files": 12,
    "mb": 1,
    "file_mb": 0.5,
    "types": [
      "string"
    ]
  },
  "used": {
    "files": 12,
    "mb": 0.5
  },
  "documents": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "ext": "string",
      "size": 12,
      "status": "uploading",
      "progress": 0.5,
      "chunks": 12,
      "error": "string",
      "engine": "ragflow",
      "source_url": "https://example.com/file.jpg",
      "created_at": 1767225600000,
      "updated_at": 1767225600000
    }
  ],
  "document": {
    "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
    "name": "Example",
    "ext": "string",
    "size": 12,
    "status": "uploading",
    "progress": 0.5,
    "chunks": 12,
    "error": "string",
    "engine": "ragflow",
    "source_url": "https://example.com/file.jpg",
    "created_at": 1767225600000,
    "updated_at": 1767225600000
  }
}

Delete a knowledge document

DEL/v1/ai-reply/documents/{id}

Delete a document: AI replies stop using it at once. Owner login only. Audited as ai_kb.delete.

Path parameters

  • idstringrequired

Responses

  • 200Deleted
    Response fields
    • availablebooleanrequired
      Whether the platform offers knowledge documents (switched off by the operator = false)
    • limitsobjectrequired
    • limits.filesintegerrequired
      Documents allowed by the plan (0 = not included)
    • limits.mbintegerrequired
    • limits.file_mbnumberrequired
      Largest single file
    • limits.typesarray of stringrequired
    • usedobjectrequired
    • used.filesintegerrequired
    • used.mbnumberrequired
    • documentsarray of objectrequired
    • documents[].idstringrequired
    • documents[].namestringrequired
      File name as uploaded
    • documents[].extstringrequired
      pdf, docx, xlsx, csv, txt, md or html
    • documents[].sizeintegerrequired
      Bytes
    • documents[].statusstringrequired
      parsing while the engine reads the file (seconds to a few minutes), ready when AI replies use it, failed with error (e.g. a scanned PDF without text), deleting while the engine copy is removed
      One of: uploading, parsing, ready, failed, deleting
    • documents[].progressnumberrequired
      0–1 while parsing
    • documents[].chunksintegerrequired
      Passages the file was cut into
    • documents[].errorstring | nullrequired
    • documents[].enginestringrequired
      Who read it: ragflow (the knowledge engine) or local (the backup reader on the API server, used when the engine is not set up, unreachable or failed the file)
      One of: ragflow, local
    • documents[].source_urlstring | nullrequired
      The web page a document was read from (POST /v1/ai-reply/documents/url); name is then the page title
    • documents[].created_atintegerrequired
    • documents[].updated_atintegerrequired
    • okbooleanrequired
  • 403Not the owner
  • 404Not found
Request
curl -X DELETE "https://api.chatkomodo.com/v1/ai-reply/documents/ID" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/documents/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/ai-reply/documents/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/ai-reply/documents/ID', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "available": true,
  "limits": {
    "files": 12,
    "mb": 1,
    "file_mb": 0.5,
    "types": [
      "string"
    ]
  },
  "used": {
    "files": 12,
    "mb": 0.5
  },
  "documents": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "ext": "string",
      "size": 12,
      "status": "uploading",
      "progress": 0.5,
      "chunks": 12,
      "error": "string",
      "engine": "ragflow",
      "source_url": "https://example.com/file.jpg",
      "created_at": 1767225600000,
      "updated_at": 1767225600000
    }
  ],
  "ok": true
}

Read a failed document again

POST/v1/ai-reply/documents/{id}/retry

Read a failed document again. Owner login only.

Path parameters

  • idstringrequired

Responses

  • 200Reading again
    Response fields
    • documentobjectrequired
    • document.idstringrequired
    • document.namestringrequired
      File name as uploaded
    • document.extstringrequired
      pdf, docx, xlsx, csv, txt, md or html
    • document.sizeintegerrequired
      Bytes
    • document.statusstringrequired
      parsing while the engine reads the file (seconds to a few minutes), ready when AI replies use it, failed with error (e.g. a scanned PDF without text), deleting while the engine copy is removed
      One of: uploading, parsing, ready, failed, deleting
    • document.progressnumberrequired
      0–1 while parsing
    • document.chunksintegerrequired
      Passages the file was cut into
    • document.errorstring | nullrequired
    • document.enginestringrequired
      Who read it: ragflow (the knowledge engine) or local (the backup reader on the API server, used when the engine is not set up, unreachable or failed the file)
      One of: ragflow, local
    • document.source_urlstring | nullrequired
      The web page a document was read from (POST /v1/ai-reply/documents/url); name is then the page title
    • document.created_atintegerrequired
    • document.updated_atintegerrequired
  • 403Not the owner
  • 404Not found
  • 409NOT_FAILED
  • 502ENGINE_ERROR — the knowledge engine did not accept the request; try again
  • 503NOT_CONFIGURED or PLATFORM_FULL
Request
curl -X POST "https://api.chatkomodo.com/v1/ai-reply/documents/ID/retry" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/documents/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/ai-reply/documents/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/ai-reply/documents/ID/retry', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "document": {
    "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
    "name": "Example",
    "ext": "string",
    "size": 12,
    "status": "uploading",
    "progress": 0.5,
    "chunks": 12,
    "error": "string",
    "engine": "ragflow",
    "source_url": "https://example.com/file.jpg",
    "created_at": 1767225600000,
    "updated_at": 1767225600000
  }
}

Search the knowledge documents

POST/v1/ai-reply/documents/search

The document passages an AI reply would get for question — to check that a document answers what customers ask. Does not call the AI and does not use the AI quota. Owner login only.

Request body

  • questionstringrequired
    1–2 000 characters

Responses

  • 200Passages, best first
    Response fields
    • resultsarray of objectrequired
    • results[].documentstringrequired
      Document name, or :faq for a saved answer
    • results[].textstringrequired
    • results[].faqboolean
    • msnumberrequired
  • 400VALIDATION_FAILED, EMPTY_FILE, BAD_FILE, BAD_ENCODING
  • 403Not the owner
Request
curl -X POST "https://api.chatkomodo.com/v1/ai-reply/documents/search" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "question": "How much is delivery to Bandung?"
}'
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/documents/search', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "question": "How much is delivery to Bandung?"
  }),
});
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/ai-reply/documents/search",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "question": "How much is delivery to Bandung?",
    },
    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/ai-reply/documents/search', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'question' => 'How much is delivery to Bandung?',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "results": [
    {
      "document": "string",
      "text": "Hi Sam, your order has shipped.",
      "faq": true
    }
  ],
  "ms": 0.5
}

Add a web page as a document

POST/v1/ai-reply/documents/url

Add a public web page (FAQ, shipping, prices) as a document: its text is read once (HTML or plain text, up to 3 MB, up to 4 redirects; private and local addresses are refused) and kept with Source: and Title: lines; name is the page title and source_url the final address. Pages that need JavaScript to show their text cannot be read (NO_TEXT). Counts as one file of the plan. Owner login only; shares the upload limit. Audited as ai_kb.upload (detail link <url>).

Request body

  • urlstring (uri)required
    Up to 2 000 characters

Responses

  • 201Added
    Response fields
    • availablebooleanrequired
      Whether the platform offers knowledge documents (switched off by the operator = false)
    • limitsobjectrequired
    • limits.filesintegerrequired
      Documents allowed by the plan (0 = not included)
    • limits.mbintegerrequired
    • limits.file_mbnumberrequired
      Largest single file
    • limits.typesarray of stringrequired
    • usedobjectrequired
    • used.filesintegerrequired
    • used.mbnumberrequired
    • documentsarray of objectrequired
    • documents[].idstringrequired
    • documents[].namestringrequired
      File name as uploaded
    • documents[].extstringrequired
      pdf, docx, xlsx, csv, txt, md or html
    • documents[].sizeintegerrequired
      Bytes
    • documents[].statusstringrequired
      parsing while the engine reads the file (seconds to a few minutes), ready when AI replies use it, failed with error (e.g. a scanned PDF without text), deleting while the engine copy is removed
      One of: uploading, parsing, ready, failed, deleting
    • documents[].progressnumberrequired
      0–1 while parsing
    • documents[].chunksintegerrequired
      Passages the file was cut into
    • documents[].errorstring | nullrequired
    • documents[].enginestringrequired
      Who read it: ragflow (the knowledge engine) or local (the backup reader on the API server, used when the engine is not set up, unreachable or failed the file)
      One of: ragflow, local
    • documents[].source_urlstring | nullrequired
      The web page a document was read from (POST /v1/ai-reply/documents/url); name is then the page title
    • documents[].created_atintegerrequired
    • documents[].updated_atintegerrequired
    • documentobjectrequired
    • document.idstringrequired
    • document.namestringrequired
      File name as uploaded
    • document.extstringrequired
      pdf, docx, xlsx, csv, txt, md or html
    • document.sizeintegerrequired
      Bytes
    • document.statusstringrequired
      parsing while the engine reads the file (seconds to a few minutes), ready when AI replies use it, failed with error (e.g. a scanned PDF without text), deleting while the engine copy is removed
      One of: uploading, parsing, ready, failed, deleting
    • document.progressnumberrequired
      0–1 while parsing
    • document.chunksintegerrequired
      Passages the file was cut into
    • document.errorstring | nullrequired
    • document.enginestringrequired
      Who read it: ragflow (the knowledge engine) or local (the backup reader on the API server, used when the engine is not set up, unreachable or failed the file)
      One of: ragflow, local
    • document.source_urlstring | nullrequired
      The web page a document was read from (POST /v1/ai-reply/documents/url); name is then the page title
    • document.created_atintegerrequired
    • document.updated_atintegerrequired
  • 400VALIDATION_FAILED or URL_BLOCKED (private / local address)
  • 402NOT_IN_PLAN or LIMIT_REACHED (files or MB of the plan)
  • 403Not the owner
  • 413FILE_TOO_LARGE — page over 3 MB
  • 415UNSUPPORTED_TYPE — not a web page or text (download the file and upload it)
  • 422FETCH_FAILED (unreachable, HTTP error, too many redirects) or NO_TEXT
  • 429Too many uploads
  • 503NOT_CONFIGURED or PLATFORM_FULL
Request
curl -X POST "https://api.chatkomodo.com/v1/ai-reply/documents/url" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/faq"
}'
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/documents/url', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "url": "https://example.com/faq"
  }),
});
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/ai-reply/documents/url",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "url": "https://example.com/faq",
    },
    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/ai-reply/documents/url', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'url' => 'https://example.com/faq',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 201
{
  "available": true,
  "limits": {
    "files": 12,
    "mb": 1,
    "file_mb": 0.5,
    "types": [
      "string"
    ]
  },
  "used": {
    "files": 12,
    "mb": 0.5
  },
  "documents": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "ext": "string",
      "size": 12,
      "status": "uploading",
      "progress": 0.5,
      "chunks": 12,
      "error": "string",
      "engine": "ragflow",
      "source_url": "https://example.com/file.jpg",
      "created_at": 1767225600000,
      "updated_at": 1767225600000
    }
  ],
  "document": {
    "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
    "name": "Example",
    "ext": "string",
    "size": 12,
    "status": "uploading",
    "progress": 0.5,
    "chunks": 12,
    "error": "string",
    "engine": "ragflow",
    "source_url": "https://example.com/file.jpg",
    "created_at": 1767225600000,
    "updated_at": 1767225600000
  }
}

Read a web page again

POST/v1/ai-reply/documents/{id}/refresh

Read a web document's page again (after the page changed). The new copy gets a new id and replaces the old one once it is in; the plan limit counts them as one. Owner login only. Audited as ai_kb.upload (detail refresh <url>).

Path parameters

  • idstringrequired

Responses

  • 201Read again
    Response fields
    • availablebooleanrequired
      Whether the platform offers knowledge documents (switched off by the operator = false)
    • limitsobjectrequired
    • limits.filesintegerrequired
      Documents allowed by the plan (0 = not included)
    • limits.mbintegerrequired
    • limits.file_mbnumberrequired
      Largest single file
    • limits.typesarray of stringrequired
    • usedobjectrequired
    • used.filesintegerrequired
    • used.mbnumberrequired
    • documentsarray of objectrequired
    • documents[].idstringrequired
    • documents[].namestringrequired
      File name as uploaded
    • documents[].extstringrequired
      pdf, docx, xlsx, csv, txt, md or html
    • documents[].sizeintegerrequired
      Bytes
    • documents[].statusstringrequired
      parsing while the engine reads the file (seconds to a few minutes), ready when AI replies use it, failed with error (e.g. a scanned PDF without text), deleting while the engine copy is removed
      One of: uploading, parsing, ready, failed, deleting
    • documents[].progressnumberrequired
      0–1 while parsing
    • documents[].chunksintegerrequired
      Passages the file was cut into
    • documents[].errorstring | nullrequired
    • documents[].enginestringrequired
      Who read it: ragflow (the knowledge engine) or local (the backup reader on the API server, used when the engine is not set up, unreachable or failed the file)
      One of: ragflow, local
    • documents[].source_urlstring | nullrequired
      The web page a document was read from (POST /v1/ai-reply/documents/url); name is then the page title
    • documents[].created_atintegerrequired
    • documents[].updated_atintegerrequired
    • documentobjectrequired
    • document.idstringrequired
    • document.namestringrequired
      File name as uploaded
    • document.extstringrequired
      pdf, docx, xlsx, csv, txt, md or html
    • document.sizeintegerrequired
      Bytes
    • document.statusstringrequired
      parsing while the engine reads the file (seconds to a few minutes), ready when AI replies use it, failed with error (e.g. a scanned PDF without text), deleting while the engine copy is removed
      One of: uploading, parsing, ready, failed, deleting
    • document.progressnumberrequired
      0–1 while parsing
    • document.chunksintegerrequired
      Passages the file was cut into
    • document.errorstring | nullrequired
    • document.enginestringrequired
      Who read it: ragflow (the knowledge engine) or local (the backup reader on the API server, used when the engine is not set up, unreachable or failed the file)
      One of: ragflow, local
    • document.source_urlstring | nullrequired
      The web page a document was read from (POST /v1/ai-reply/documents/url); name is then the page title
    • document.created_atintegerrequired
    • document.updated_atintegerrequired
  • 400URL_BLOCKED
  • 403Not the owner
  • 404Not found
  • 409NOT_A_PAGE — the document was uploaded as a file
  • 422FETCH_FAILED or NO_TEXT
Request
curl -X POST "https://api.chatkomodo.com/v1/ai-reply/documents/ID/refresh" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/documents/ID/refresh', {
  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/ai-reply/documents/ID/refresh",
    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/ai-reply/documents/ID/refresh', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 201
{
  "available": true,
  "limits": {
    "files": 12,
    "mb": 1,
    "file_mb": 0.5,
    "types": [
      "string"
    ]
  },
  "used": {
    "files": 12,
    "mb": 0.5
  },
  "documents": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "ext": "string",
      "size": 12,
      "status": "uploading",
      "progress": 0.5,
      "chunks": 12,
      "error": "string",
      "engine": "ragflow",
      "source_url": "https://example.com/file.jpg",
      "created_at": 1767225600000,
      "updated_at": 1767225600000
    }
  ],
  "document": {
    "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
    "name": "Example",
    "ext": "string",
    "size": 12,
    "status": "uploading",
    "progress": 0.5,
    "chunks": 12,
    "error": "string",
    "engine": "ragflow",
    "source_url": "https://example.com/file.jpg",
    "created_at": 1767225600000,
    "updated_at": 1767225600000
  }
}

List saved answers

GET/v1/ai-reply/faq

Saved answers: question/answer pairs written by the business. AI replies find them like document passages and prefer them (the business wrote them for exactly that question). Readable by agents.

Responses

  • 200Newest first
    Response fields
    • itemsarray of objectrequired
    • items[].idstringrequired
    • items[].questionstringrequired
    • items[].answerstringrequired
    • items[].sourcestringrequired
      unanswered: written from a question the assistant could not answer
      One of: manual, unanswered
    • items[].created_atintegerrequired
    • items[].updated_atintegerrequired
Request
curl "https://api.chatkomodo.com/v1/ai-reply/faq" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/faq', {
  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/ai-reply/faq",
    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/ai-reply/faq', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "items": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "question": "Do you deliver on Sunday?",
      "answer": "Hi Sam, your order has shipped.",
      "source": "manual",
      "created_at": 1767225600000,
      "updated_at": 1767225600000
    }
  ]
}

Add a saved answer

POST/v1/ai-reply/faq

Add a saved answer (at most 500 per business). Used by the next AI reply at once. Owner login only. Audited as ai_kb.faq (detail add).

Request body

  • questionstringrequired
    1–500 characters
  • answerstringrequired
    1–2 000 characters

Responses

  • 201Saved
    Response fields
    • faqobjectrequired
    • faq.idstringrequired
    • faq.questionstringrequired
    • faq.answerstringrequired
    • faq.sourcestringrequired
      unanswered: written from a question the assistant could not answer
      One of: manual, unanswered
    • faq.created_atintegerrequired
    • faq.updated_atintegerrequired
  • 400VALIDATION_FAILED
  • 402LIMIT_REACHED
  • 403Not the owner
Request
curl -X POST "https://api.chatkomodo.com/v1/ai-reply/faq" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "question": "Do you deliver on Sunday?",
  "answer": "Yes, Sunday deliveries leave at 10:00 and arrive the same day in Jakarta."
}'
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/faq', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "question": "Do you deliver on Sunday?",
    "answer": "Yes, Sunday deliveries leave at 10:00 and arrive the same day in Jakarta."
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.post(
    "https://api.chatkomodo.com/v1/ai-reply/faq",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "question": "Do you deliver on Sunday?",
        "answer": "Yes, Sunday deliveries leave at 10:00 and arrive the same day in Jakarta.",
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/ai-reply/faq', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'question' => 'Do you deliver on Sunday?',
        'answer' => 'Yes, Sunday deliveries leave at 10:00 and arrive the same day in Jakarta.',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 201
{
  "faq": {
    "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
    "question": "Do you deliver on Sunday?",
    "answer": "Hi Sam, your order has shipped.",
    "source": "manual",
    "created_at": 1767225600000,
    "updated_at": 1767225600000
  }
}

Change a saved answer

PUT/v1/ai-reply/faq/{id}

Change a saved answer. Owner login only. Audited as ai_kb.faq (detail edit).

Path parameters

  • idstringrequired

Request body

  • questionstringrequired
    1–500 characters
  • answerstringrequired
    1–2 000 characters

Responses

  • 200Saved
    Response fields
    • faqobjectrequired
    • faq.idstringrequired
    • faq.questionstringrequired
    • faq.answerstringrequired
    • faq.sourcestringrequired
      unanswered: written from a question the assistant could not answer
      One of: manual, unanswered
    • faq.created_atintegerrequired
    • faq.updated_atintegerrequired
  • 400VALIDATION_FAILED
  • 403Not the owner
  • 404Not found
Request
curl -X PUT "https://api.chatkomodo.com/v1/ai-reply/faq/ID" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "question": "Do you deliver on Sunday?",
  "answer": "Yes, from 10:00."
}'
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/faq/ID', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "question": "Do you deliver on Sunday?",
    "answer": "Yes, from 10:00."
  }),
});
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/ai-reply/faq/ID",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "question": "Do you deliver on Sunday?",
        "answer": "Yes, from 10:00.",
    },
    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/ai-reply/faq/ID', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'question' => 'Do you deliver on Sunday?',
        'answer' => 'Yes, from 10:00.',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "faq": {
    "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
    "question": "Do you deliver on Sunday?",
    "answer": "Hi Sam, your order has shipped.",
    "source": "manual",
    "created_at": 1767225600000,
    "updated_at": 1767225600000
  }
}

Delete a saved answer

DEL/v1/ai-reply/faq/{id}

Delete a saved answer. Owner login only. Audited as ai_kb.faq (detail delete).

Path parameters

  • idstringrequired

Responses

  • 200Deleted
    Response fields
    • okbooleanrequired
  • 403Not the owner
  • 404Not found
Request
curl -X DELETE "https://api.chatkomodo.com/v1/ai-reply/faq/ID" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/faq/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/ai-reply/faq/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/ai-reply/faq/ID', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

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

List unanswered questions

GET/v1/ai-reply/unanswered

Questions the AI assistant could not answer from the business information (it told the customer the team will follow up). Open ones first, most asked first; status=all also lists answered and dismissed ones. At most 300 open per business (the least asked make room). Readable by agents.

Query parameters

  • statusstring
    One of: open, all

Responses

  • 200List
    Response fields
    • itemsarray of objectrequired
    • items[].idstringrequired
    • items[].questionstringrequired
      The customer's message (first wording seen)
    • items[].countintegerrequired
      How many times it was asked (same words, ignoring case and punctuation)
    • items[].session_idstring | nullrequired
    • items[].chat_idstring | nullrequired
      The latest chat that asked
    • items[].replystring | nullrequired
      What the assistant answered
    • items[].statusstringrequired
      One of: open, answered, dismissed
    • items[].faq_idstring | nullrequired
    • items[].created_atintegerrequired
    • items[].last_atintegerrequired
    • openintegerrequired
Request
curl "https://api.chatkomodo.com/v1/ai-reply/unanswered" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/unanswered', {
  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/ai-reply/unanswered",
    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/ai-reply/unanswered', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "items": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "question": "Do you deliver on Sunday?",
      "count": 12,
      "session_id": "YOUR_DEVICE_ID",
      "chat_id": "+447700900123",
      "reply": "Hi Sam, your order has shipped.",
      "status": "open",
      "faq_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "created_at": 1767225600000,
      "last_at": 1767225600000
    }
  ],
  "open": 1
}

Answer an unanswered question

POST/v1/ai-reply/unanswered/{id}/answer

Answer a question once: it becomes a saved answer (optionally with a cleaner question) that the next AI reply uses, and leaves the open list. Owner login only. Audited as ai_kb.faq (detail answer).

Path parameters

  • idstringrequired

Request body

  • answerstringrequired
    1–2 000 characters
  • questionstring
    1–500 characters

Responses

  • 200Saved
    Response fields
    • okbooleanrequired
    • faqobjectrequired
    • faq.idstringrequired
    • faq.questionstringrequired
    • faq.answerstringrequired
    • faq.sourcestringrequired
      unanswered: written from a question the assistant could not answer
      One of: manual, unanswered
    • faq.created_atintegerrequired
    • faq.updated_atintegerrequired
    • openintegerrequired
  • 400VALIDATION_FAILED
  • 402LIMIT_REACHED (500 saved answers)
  • 403Not the owner
  • 404Not found
Request
curl -X POST "https://api.chatkomodo.com/v1/ai-reply/unanswered/ID/answer" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "answer": "Yes, we take children from age 2."
}'
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/unanswered/ID/answer', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "answer": "Yes, we take children from age 2."
  }),
});
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/ai-reply/unanswered/ID/answer",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "answer": "Yes, we take children from age 2.",
    },
    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/ai-reply/unanswered/ID/answer', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'answer' => 'Yes, we take children from age 2.',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "ok": true,
  "faq": {
    "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
    "question": "Do you deliver on Sunday?",
    "answer": "Hi Sam, your order has shipped.",
    "source": "manual",
    "created_at": 1767225600000,
    "updated_at": 1767225600000
  },
  "open": 1
}

Dismiss an unanswered question

POST/v1/ai-reply/unanswered/{id}/dismiss

Close an open question without answering it. Owner login only.

Path parameters

  • idstringrequired

Responses

  • 200Closed
    Response fields
    • okbooleanrequired
    • openintegerrequired
  • 403Not the owner
  • 404Not found or not open
Request
curl -X POST "https://api.chatkomodo.com/v1/ai-reply/unanswered/ID/dismiss" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/unanswered/ID/dismiss', {
  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/ai-reply/unanswered/ID/dismiss",
    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/ai-reply/unanswered/ID/dismiss', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

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

Get the AI report

GET/v1/ai-reply/report

What the AI assistant did over the last 7, 30 or 90 days (default 30), by day in the business's time zone: replies, replies where it was unsure, handoffs to a person, chats served, answered rate (replies / all), most asked questions, documents used most, open unanswered questions, and this month's quota. Kept 90 days.

Query parameters

  • daysstring
    One of: 7, 30, 90

Responses

  • 200Report
    Response fields
    • daysintegerrequired
    • totalsobjectrequired
    • totals.repliesintegerrequired
    • totals.unsureintegerrequired
    • totals.handoffsintegerrequired
    • totals.chatsintegerrequired
    • totals.answered_ratenumber | nullrequired
    • dailyarray of objectrequired
    • daily[].daystringrequired
      YYYY-MM-DD
    • daily[].repliesintegerrequired
    • daily[].unsureintegerrequired
    • daily[].handoffsintegerrequired
    • top_questionsarray of objectrequired
    • top_questions[].questionstringrequired
    • top_questions[].countintegerrequired
    • top_questions[].unsureintegerrequired
    • top_sourcesarray of objectrequired
    • top_sources[].sourcestringrequired
      Document name or :faq
    • top_sources[].countintegerrequired
    • open_unansweredintegerrequired
    • quotaobjectrequired
    • quota.limitintegerrequired
      AI replies included in the plan per month (0 = not included)
    • quota.usedintegerrequired
    • quota.periodstringrequired
      YYYY-MM
  • 400VALIDATION_FAILED
Request
curl "https://api.chatkomodo.com/v1/ai-reply/report?days=30" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/report?days=30', {
  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/ai-reply/report?days=30",
    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/ai-reply/report?days=30', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "days": 1,
  "totals": {
    "replies": 1,
    "unsure": 1,
    "handoffs": 1,
    "chats": 1,
    "answered_rate": 0.5
  },
  "daily": [
    {
      "day": "string",
      "replies": 1,
      "unsure": 1,
      "handoffs": 1
    }
  ],
  "top_questions": [
    {
      "question": "Do you deliver on Sunday?",
      "count": 12,
      "unsure": 1
    }
  ],
  "top_sources": [
    {
      "source": "string",
      "count": 12
    }
  ],
  "open_unanswered": 1,
  "quota": {
    "limit": 50,
    "used": 12,
    "period": "string"
  }
}

Suggest a reply for a chat

POST/v1/ai-reply/suggest

A draft of the next reply in a chat for the person answering it, from the last week of the conversation, the business information and the documents. Nothing is sent. Counts as one AI reply. Owner, and agents for chats assigned to them or unassigned.

Request body

  • session_idstringrequired
  • chat_idstringrequired
  • langstring
    Summary language (default en)
    One of: en, id

Responses

  • 200Draft
    Response fields
    • textstringrequired
    • msintegerrequired
    • sourcesarray of stringrequired
    • unsurebooleanrequired
      The business information did not answer it: check before sending
    • quotaobjectrequired
    • quota.limitintegerrequired
      AI replies included in the plan per month (0 = not included)
    • quota.usedintegerrequired
    • quota.periodstringrequired
      YYYY-MM
  • 400VALIDATION_FAILED (session not yours, chat missing)
  • 402LIMIT_REACHED — no AI replies left this month
  • 403An agent asking about a chat assigned to someone else
  • 422NOTHING_TO_DO — no recent text messages in the chat
  • 429More than 20 a minute per person
  • 502UPSTREAM_ERROR
  • 503NOT_CONFIGURED — no AI model on the platform
Request
curl -X POST "https://api.chatkomodo.com/v1/ai-reply/suggest" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "session_id": "YOUR_DEVICE_ID",
  "chat_id": "[email protected]"
}'
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/suggest', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "session_id": "YOUR_DEVICE_ID",
    "chat_id": "[email protected]"
  }),
});
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/ai-reply/suggest",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "session_id": "YOUR_DEVICE_ID",
        "chat_id": "[email protected]",
    },
    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/ai-reply/suggest', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'session_id' => 'YOUR_DEVICE_ID',
        'chat_id' => '[email protected]',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "text": "Hi Sam, your order has shipped.",
  "ms": 1,
  "sources": [
    "string"
  ],
  "unsure": true,
  "quota": {
    "limit": 50,
    "used": 12,
    "period": "string"
  }
}

Summarize a chat

POST/v1/ai-reply/summary

A 3–5 line summary of a conversation (last two weeks) for whoever takes it over: what the customer wants, what was promised, open questions, next step. Counts as one AI reply. Same access as suggest.

Request body

  • session_idstringrequired
  • chat_idstringrequired
  • langstring
    Summary language (default en)
    One of: en, id

Responses

  • 200Summary
    Response fields
    • textstringrequired
    • msintegerrequired
    • quotaobjectrequired
    • quota.limitintegerrequired
      AI replies included in the plan per month (0 = not included)
    • quota.usedintegerrequired
    • quota.periodstringrequired
      YYYY-MM
  • 400VALIDATION_FAILED (session not yours, chat missing)
  • 402LIMIT_REACHED — no AI replies left this month
  • 403An agent asking about a chat assigned to someone else
  • 422NOTHING_TO_DO — no recent text messages in the chat
  • 429More than 20 a minute per person
  • 502UPSTREAM_ERROR
  • 503NOT_CONFIGURED — no AI model on the platform
Request
curl -X POST "https://api.chatkomodo.com/v1/ai-reply/summary" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "session_id": "YOUR_DEVICE_ID",
  "chat_id": "[email protected]",
  "lang": "en"
}'
const res = await fetch('https://api.chatkomodo.com/v1/ai-reply/summary', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "session_id": "YOUR_DEVICE_ID",
    "chat_id": "[email protected]",
    "lang": "en"
  }),
});
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/ai-reply/summary",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "session_id": "YOUR_DEVICE_ID",
        "chat_id": "[email protected]",
        "lang": "en",
    },
    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/ai-reply/summary', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'session_id' => 'YOUR_DEVICE_ID',
        'chat_id' => '[email protected]',
        'lang' => 'en',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "text": "Hi Sam, your order has shipped.",
  "ms": 1,
  "quota": {
    "limit": 50,
    "used": 12,
    "period": "string"
  }
}