ChatKomodo Docs Home Sign in

API reference · Integrations

Sign-in security

The owner's dashboard sign-in: signed-in devices and signing them out, an authenticator code at every sign-in, passkeys. Dashboard login only.

Sign-in security status

GET/v1/security

Whether the authenticator app is set up and required at every sign-in, how many passkeys and signed-in sessions there are.

Responses

  • 200Status
    Response fields
    • totp_enabledbooleanrequired
    • require_totpbooleanrequired
    • passkeysintegerrequired
    • sessionsintegerrequired
  • 401Missing, invalid or expired credentials
  • 403Forbidden (FORBIDDEN): only the business owner's dashboard login, not an agent or an API key
Request
curl "https://api.chatkomodo.com/v1/security" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/security', {
  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/security",
    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/security', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

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

Require the authenticator code at every sign-in

PUT/v1/security

With require_totp: true, signing in with the WhatsApp code (or the access code) asks for the authenticator code as well (/auth/verify-mfa). Needs the authenticator app set up first. Recorded as security.require_totp.

Request body

  • require_totpbooleanrequired

Responses

  • 200Saved
    Response fields
    • okbooleanrequired
    • require_totpbooleanrequired
  • 400Body is not valid JSON (INVALID_JSON) or fails validation (VALIDATION_FAILED)
  • 401Missing, invalid or expired credentials
  • 403Forbidden (FORBIDDEN): only the business owner's dashboard login, not an agent or an API key
  • 404Dashboard login not found, or it belongs to another tenant (NOT_FOUND)
  • 409TOTP_NOT_ENABLED: set up the authenticator app first
Request
curl -X PUT "https://api.chatkomodo.com/v1/security" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "require_totp": true
}'
const res = await fetch('https://api.chatkomodo.com/v1/security', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "require_totp": true
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.put(
    "https://api.chatkomodo.com/v1/security",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "require_totp": True,
    },
    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/security', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'require_totp' => true,
    ],
]);

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

Signed-in devices

GET/v1/security/sessions

Dashboard sessions that are still signed in (owner first, then agents), with device, address and country. A sign-in from a device not seen for that person in 180 days notifies the owner (push, and WhatsApp for the owner's own sign-ins) and is recorded as auth.new_device.

Responses

  • 200Sessions
    Response fields
    • sessionsarray of objectrequired
    • sessions[].idstringrequired
    • sessions[].rolestringrequired
      One of: admin, agent
    • sessions[].agent_idstring | nullrequired
    • sessions[].agent_namestring | nullrequired
    • sessions[].methodstringrequired
      How this session signed in
      One of: otp, otp+totp, totp, access_code, passkey
    • sessions[].ipstring | nullrequired
    • sessions[].countrystring | nullrequired
      ISO code as Cloudflare reports it
    • sessions[].devicestringrequired
      Browser and operating system, e.g. Chrome on Windows
    • sessions[].created_atintegerrequired
      Unix time in milliseconds
    • sessions[].last_seen_atintegerrequired
      Sign-in or last token refresh
    • sessions[].currentbooleanrequired
      The session making this request
  • 401Missing, invalid or expired credentials
  • 403Forbidden (FORBIDDEN): only the business owner's dashboard login, not an agent or an API key
Request
curl "https://api.chatkomodo.com/v1/security/sessions" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/security/sessions', {
  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/security/sessions",
    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/security/sessions', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "sessions": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "role": "admin",
      "agent_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "agent_name": "string",
      "method": "otp",
      "ip": "string",
      "country": "GB",
      "device": "YOUR_DEVICE_ID",
      "created_at": 1767225600000,
      "last_seen_at": 1767225600000,
      "current": true
    }
  ]
}

Sign one device out

POST/v1/security/sessions/{id}/revoke

Ends a session: its access token stops working at once and its refresh token is deleted. Recorded as security.sign_out.

Path parameters

  • idstringrequired
    Session id

Responses

  • 200Signed out
    Response fields
    • okbooleanrequired
    • currentbooleanrequired
      true when it was the calling session
  • 401Missing, invalid or expired credentials
  • 403Forbidden (FORBIDDEN): only the business owner's dashboard login, not an agent or an API key
  • 404Session not found, or it belongs to another tenant (NOT_FOUND)
Request
curl -X POST "https://api.chatkomodo.com/v1/security/sessions/ID/revoke" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/security/sessions/ID/revoke', {
  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/security/sessions/ID/revoke",
    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/security/sessions/ID/revoke', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

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

Sign out every other device

POST/v1/security/sessions/revoke-all

Ends every owner session except the calling one; with include_agents: true also every agent session. Recorded as security.sign_out_all.

Request body

  • include_agentsboolean

Responses

  • 200Signed out
    Response fields
    • okbooleanrequired
    • signed_outintegerrequired
  • 401Missing, invalid or expired credentials
  • 403Forbidden (FORBIDDEN): only the business owner's dashboard login, not an agent or an API key
Request
curl -X POST "https://api.chatkomodo.com/v1/security/sessions/revoke-all" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "include_agents": true
}'
const res = await fetch('https://api.chatkomodo.com/v1/security/sessions/revoke-all', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "include_agents": true
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.post(
    "https://api.chatkomodo.com/v1/security/sessions/revoke-all",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "include_agents": True,
    },
    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/security/sessions/revoke-all', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'include_agents' => true,
    ],
]);

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

List passkeys

GET/v1/security/passkeys

The owner's passkeys (sign-in without a code, phishing-resistant).

Responses

  • 200Passkeys
    Response fields
    • passkeysarray of objectrequired
    • passkeys[].idstringrequired
    • passkeys[].namestringrequired
    • passkeys[].created_atintegerrequired
      Unix time in milliseconds
    • passkeys[].last_used_atinteger | nullrequired
      Last sign-in with it
  • 401Missing, invalid or expired credentials
  • 403Forbidden (FORBIDDEN): only the business owner's dashboard login, not an agent or an API key
Request
curl "https://api.chatkomodo.com/v1/security/passkeys" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/security/passkeys', {
  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/security/passkeys",
    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/security/passkeys', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "passkeys": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "name": "Example",
      "created_at": 1767225600000,
      "last_used_at": 1767225600000
    }
  ]
}

Add a passkey

POST/v1/security/passkeys

Verifies the browser's registration response and stores the passkey. Recorded as security.passkey_add.

Request body

  • challenge_idstringrequired
  • responseobjectrequired
    RegistrationResponseJSON
  • namestring
    Label, e.g. "MacBook" (default Passkey)
    Up to 60 characters

Responses

  • 201Added
    Response fields
    • okbooleanrequired
    • passkeyobjectrequired
    • passkey.idstringrequired
    • passkey.namestringrequired
  • 400Missing fields, expired request (CHALLENGE_EXPIRED) or failed verification (PASSKEY_INVALID)
  • 401Missing, invalid or expired credentials
  • 403Forbidden (FORBIDDEN): only the business owner's dashboard login, not an agent or an API key
Request
curl -X POST "https://api.chatkomodo.com/v1/security/passkeys" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "challenge_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
  "response": {},
  "name": "Example"
}'
const res = await fetch('https://api.chatkomodo.com/v1/security/passkeys', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "challenge_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
    "response": {},
    "name": "Example"
  }),
});
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/security/passkeys",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "challenge_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
        "response": {},
        "name": "Example",
    },
    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/security/passkeys', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'challenge_id' => '01JA5V6Q8M2H7G4K9P3T0R1S2X',
        'response' => [],
        'name' => 'Example',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 201
{
  "ok": true,
  "passkey": {
    "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
    "name": "Example"
  }
}

Start adding a passkey

POST/v1/security/passkeys/options

WebAuthn registration options (resident key required). Pass options to the browser (navigator.credentials.create / @simplewebauthn/browser startRegistration), then send the result to POST /v1/security/passkeys within 5 minutes. At most 10 passkeys.

Responses

  • 200Options
    Response fields
    • challenge_idstringrequired
    • optionsobjectrequired
      PublicKeyCredentialCreationOptionsJSON
  • 401Missing, invalid or expired credentials
  • 403Forbidden (FORBIDDEN): only the business owner's dashboard login, not an agent or an API key
  • 409LIMIT_REACHED: 10 passkeys already
Request
curl -X POST "https://api.chatkomodo.com/v1/security/passkeys/options" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/security/passkeys/options', {
  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/security/passkeys/options",
    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/security/passkeys/options', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "challenge_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
  "options": {}
}

Rename a passkey

PATCH/v1/security/passkeys/{id}

Changes the label shown in Settings → Security.

Path parameters

  • idstringrequired
    Passkey id

Request body

  • namestringrequired
    1–60 characters

Responses

  • 200Renamed
    Response fields
    • okbooleanrequired
  • 400Body is not valid JSON (INVALID_JSON) or fails validation (VALIDATION_FAILED)
  • 401Missing, invalid or expired credentials
  • 403Forbidden (FORBIDDEN): only the business owner's dashboard login, not an agent or an API key
  • 404Passkey not found, or it belongs to another tenant (NOT_FOUND)
Request
curl -X PATCH "https://api.chatkomodo.com/v1/security/passkeys/ID" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Example"
}'
const res = await fetch('https://api.chatkomodo.com/v1/security/passkeys/ID', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Example"
  }),
});
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/security/passkeys/ID",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "name": "Example",
    },
    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/security/passkeys/ID', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'name' => 'Example',
    ],
]);

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

Remove a passkey

DEL/v1/security/passkeys/{id}

It can no longer sign in. Recorded as security.passkey_remove.

Path parameters

  • idstringrequired
    Passkey id

Responses

  • 200Removed
    Response fields
    • okbooleanrequired
  • 401Missing, invalid or expired credentials
  • 403Forbidden (FORBIDDEN): only the business owner's dashboard login, not an agent or an API key
  • 404Passkey not found, or it belongs to another tenant (NOT_FOUND)
Request
curl -X DELETE "https://api.chatkomodo.com/v1/security/passkeys/ID" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/security/passkeys/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/security/passkeys/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/security/passkeys/ID', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

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