The KairosAI API

A REST API over your workspace: talk to an agent, read the conversations it had, the people it talked to, the pipeline and tickets that came out of them, and the knowledge it answers from. JSON in, JSON out, one bearer token. Everything the dashboard shows you is read through these same endpoints.

Setting up the product rather than integrating with it? The platform guide covers channels, the knowledge base, the web widget and the wallet.

Quickstart

  1. Get a key. Sign in, open Settings in the dashboard, pick the Developer tab and create an API key. The key is shown once, on creation, and never again. Copy it before you dismiss the dialog.
  2. Make a call. Requests go to https://api.trykairos.in and carry the key as a bearer token. Start with the agent list: it takes no arguments, and its response carries the installation id the rest of the API is keyed on.
curl
curl https://api.trykairos.in/v1/admin/installations \
  -H "Authorization: Bearer kk_your_key_here"

Read the response. A success is a 2xx with the documented body. Any failure, of any kind, is the same four-field envelope described under Errors, so a client needs exactly one error path. If you get a 401 here, the key is wrong or revoked; if you get an empty array, the workspace has no agents yet.

Then talk to the agent. Take id from that list, open a conversation against it, and stream a reply. Those two calls are the Chat group, and they are what most integrations are for.

curl
# 1. open a conversation against that agent
curl -X POST https://api.trykairos.in/v1/agent-installations/$INSTALL_ID/conversations \
  -H "Content-Type: application/json" \
  -d '{"channel":"web"}'

# 2. send a turn and stream the reply (-N disables curl's buffering)
curl -N -X POST https://api.trykairos.in/v1/conversations/$CONVERSATION_ID/messages \
  -H "Content-Type: application/json" \
  -d '{"text":"What does the Growth plan include?"}'

No key yet? Every endpoint below carries a Try it panel. Opened, it returns that endpoint’s documented example response with no key and no network, so you can read the exact shape of what you would get back before you sign up for anything. On the read-only endpoints you can switch it to a live call against your own workspace once you do hold a key.

Authentication

Base URL: https://api.trykairos.in. Only HTTPS is served.

Authenticate with a workspace API key in the Authorization header. Keys begin with kk_.

header
Authorization: Bearer kk_your_key_here

A key is scoped to the workspace that issued it and cannot see another workspace, ever. Isolation is enforced in the database, not in application code, so there is no handler you could ask for the wrong row. There is no tenant id to pass; it comes from the key.

Keys are stored as a hash. We cannot show you an existing key, recover one, or email one to you. If a key leaks, revoke it and issue another. Treat it like a password: keep it server side, never in a browser bundle or a mobile app.

Two endpoints take no credential at all, and they are marked no api key: the Chat pair that the embedded web widget calls from a customer’s browser, where there is nowhere to keep a secret. There the installation id is the handle and the install’s origin allowlist is the gate. Set that allowlist before you put an agent in front of the public.

Endpoints marked admin session additionally require a signed-in tenant administrator. A few examples on this page therefore show $KAIROS_ADMIN_TOKEN rather than $KAIROS_API_KEY; that stands for an administrator’s session credential, and in practice those actions are done in the dashboard. See Permissions.

Permissions

An API key acts with the tenant_user role, not tenant_admin. That is deliberate. A key is a long-lived credential that ends up in CI configs and scripts and gets copied further than a browser session ever does. Holding one lets you do the day-to-day work a key is for; it does not let you change roles, alter billing, rewrite integrations, or mint more keys, and above all it cannot lock the real owner out. Those need a person who is signed in.

An API key calling an admin-only endpoint gets a 403 whose message reads API keys act with role=tenant_user and cannot perform role=tenant_admin actions. The endpoints below carry an admin session tag where this applies.

Modules

Some endpoints belong to a business module (crm, support, and others) that has to be active on your plan. When it is not, the response is a 403 with error set to module_locked and details.module naming which one. Endpoints below carry a module tag where this applies.

Errors

Every 4xx and 5xx returns the same body. Four fields, always present, whatever went wrong and wherever it went wrong.

FieldTypeDescription
errorstringA stable machine code in snake_case. This is the field to branch on. It does not change once published.
messagestringOne human sentence, safe to show a person. For a validation failure it names the offending fields.
request_idstring | nullThe same value as the X-Kairos-Request-Id response header. Log it. Quoting it to support is the difference between a diagnosis and a guess.
detailsobject | array | nullStructured payload whose shape depends on error. Absent for simple errors; on a 422 it is the per-field array, so a form can mark up individual inputs.

A fifth field, detail, is also present. It is a compatibility surface for older clients and duplicates the information above. New code should read error and message and ignore it.

422 Unprocessable Entity
{
  "error": "validation_failed",
  "message": "text: String should have at most 4000 characters",
  "request_id": "01J8ZC7B4N9K2QF0X3M6TVA5RD",
  "details": [
    {
      "type": "string_too_long",
      "loc": ["body", "text"],
      "msg": "String should have at most 4000 characters"
    }
  ],
  "detail": [
    {
      "type": "string_too_long",
      "loc": ["body", "text"],
      "msg": "String should have at most 4000 characters"
    }
  ]
}

Server faults are deliberately opaque: a 500 always returns error: "server_error" with a generic message, because the underlying text can carry a database message or a query and an error body is the one thing that reliably reaches a screenshot. The request_id is how we find the real cause. A 503 is the exception and does explain itself, since it usually means something is unconfigured and you can act on that.

Codes

errorStatusMeaning
unauthenticated401No credential, or a key that does not resolve. Check the Authorization header.
forbidden403Authenticated, but not allowed. Most often an API key attempting an action that needs a tenant-admin session.
module_locked403The business module behind this endpoint is not active on your plan. `details.module` names it.
not_found404No such record in your workspace. A record belonging to another workspace is indistinguishable from one that does not exist, on purpose.
conflict409The request contradicts the current state of the record. The message says how.
validation_failed422The body failed validation. `message` names the first offending fields; `details` is the per-field array.
rate_limited429Too many requests. See Rate limits below.
bad_request400The request was understood but is not usable as sent.
server_error5xxA fault on our side. The message is deliberately generic; quote `request_id` to support.
unavailable503A dependency this endpoint needs is not configured or is temporarily down. The message says which.

Individual endpoints may return further codes specific to what they do; they follow the same envelope, so an unrecognised error value is still safe to log and show message for.

Rate limits

The /v1/admin endpoints, which is everything on this page except the two Chat routes, are limited per credential rather than per IP address, so a noisy neighbour behind the same CDN cannot spend your budget. The default is a sustained 100 requests per second with a burst of 200. In practice this is well above what an integration needs; it exists as an abuse backstop.

The two unauthenticated Chat routes are metered separately, and much lower, because they are reachable from a customer’s browser. They share one per workspace bucket holding 100 requests, refilled at 5 per second, so starting a conversation and sending a turn draw from the same budget. Size a chat integration against that number, not the admin one.

Either way you get a 429 with a retry-after header in seconds. Honour that header. The Chat routes answer with the ordinary envelope and error: "rate_limited". The admin limiter is the one place the envelope is not used: it runs as middleware, ahead of the handler that shapes errors, so its fields sit under detail and there is no top-level error to branch on. Branch on the status for this one.

429 Too Many Requests (admin endpoints)
{
  "detail": {
    "error": "rate_limit_exceeded",
    "message": "rate limit exceeded",
    "retry_after_seconds": 0.42
  }
}

Versioning

Everything lives under /v1, and /v1 is frozen against breaking changes. We will not remove or rename a route, remove or rename a response field, narrow an accepted request, or change what an existing field means.

New routes, new optional request fields and new response fields are shipped on /v1, because they break nobody. Write your client to ignore response fields it does not recognise. A genuinely breaking change would appear under a /v2 prefix running alongside /v1, with the old routes marked deprecated and kept live through a migration window.

Streaming a reply

One endpoint does not return JSON: POST /v1/conversations/{conversation_id}/messages answers with text/event-stream and holds the connection open for the length of the agent’s turn. Every line is a Server-Sent Event: an event: name and a data: line carrying compact JSON, with a blank line between events.

Switch on the event name and ignore any you do not recognise. The stream is finished when the connection closes, which is normally just after run_completed and message_persisted.

Eventdata fieldsMeaning
run_startedthread_id, agent_idThe turn began. thread_id is the conversation id.
assistant_tokentextOne fragment of the reply as it is generated. Append them in order to render the answer live.
assistant_messagetext, input_tokens, output_tokens, cache_read_tokens, cache_write_tokensA completed model turn with its full text and token usage. There can be more than one in a turn when the agent calls a tool and then summarises. The cache counts are a subset of input_tokens, not an addition to it.
tool_call_startedtool, arguments, call_idThe agent invoked a tool.
tool_call_completedtool, call_id, result, errorThe tool returned. error is null on success.
run_completedthread_idThe turn finished normally.
run_erroredthread_id, errorThe turn failed. error is a generic sentence safe to show a person; the real cause is logged on our side against the request id.
message_persistedmessage_idThe id the assistant reply was stored under. Sent last, and only when the agent produced text.
operator_activemessageA human has taken this conversation over in the console. The user's message was recorded, the agent did not answer, and the stream ends here.
cost_exceededreason, tokens, tool_callsThe conversation hit its cost ceiling part-way through and the run was halted.
server_shutting_downreasonThe turn was cut short by a deploy. Retry it.

The seven run events also repeat their own name inside data as a type field, so a client that only reads bodies still knows what it is holding. The four the route adds around them ( message_persisted, operator_active, cost_exceeded and server_shutting_down ) do not.

A failure before the stream opens is an ordinary HTTP error with the envelope above. A failure once it is open arrives as a run_errored event on a 200, so handle both.

Webhook delivery

A subscription is an https endpoint plus a list of event names. When a subscribed event happens we POST this body to it. Manage subscriptions with the Webhooks endpoints below.

delivery
POST https://hooks.example.com/kairos
content-type: application/json
x-kairos-event: conversation.ended
x-kairos-signature: sha256=6f1b...

{
  "event": "conversation.ended",
  "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
  "data": { "conversation_id": "9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611" }
}

The envelope is always the same three keys: event, tenant_id and data. The shape of data depends on the event.

Verifying a delivery

x-kairos-signature carries sha256= followed by the hex HMAC-SHA256 of the raw request body, keyed with the subscription secret. Recompute it and compare in constant time. Reject anything that does not match: without this check the endpoint will accept a forged event from anyone who learns its URL.

node
import crypto from "node:crypto";

// Verify against the RAW request body, before any JSON parsing:
// re-serialising the parsed object changes the bytes and the
// signature will never match.
function verify(rawBody, header, secret) {
  const expected =
    "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(header ?? "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Retries and auto-disable

A delivery that fails is retried with backoff. Return a 2xx quickly and do your work afterwards; a slow handler reads as a failure. After ten consecutive final failures the subscription is disabled automatically and auto_disabled_at is set, so one bad deploy on your side does not silently lose a day of events without anyone noticing. Re-enable it with a PATCH once the endpoint is healthy.

Deliveries are at-least-once. Make your handler idempotent, and use GET /v1/admin/webhooks/{sub_id}/attempts to see what we sent and what came back.

Agents

An agent installation is one configured agent inside your workspace. Its id is what conversations, knowledge documents and phone numbers are attached to.

List agents

GET/v1/admin/installations
200

Every agent installed in your workspace, with the last heartbeat seen from each. Pass archived=true to get only archived agents instead.

Query parameters

NameTypeDescription
archivedbooleanDefault false. When true the response contains ONLY archived agents, newest-archived first.

Request

curl
curl https://api.trykairos.in/v1/admin/installations \
  -H "Authorization: Bearer $KAIROS_API_KEY"

Response

200
[
  {
    "id": "3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33",
    "agent_id": "assistant",
    "vertical_id": null,
    "enabled": true,
    "allowed_origins": ["https://example.com"],
    "config_override": {},
    "last_seen_at": "2026-07-31T09:12:44+00:00",
    "last_seen_origin": "https://example.com",
    "workforce_id": null,
    "base_agent_id": null,
    "group_name": null,
    "display_name": "Front desk",
    "archived_at": null
  }
]
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/installations

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Retrieve an agent

GET/v1/admin/installations/{install_id}
200

One installation. Returns 404 for an id that belongs to another workspace, the same as for an id that does not exist.

Path parameters

NameTypeDescription
install_iduuid
required
The installation id.

Request

curl
curl https://api.trykairos.in/v1/admin/installations/3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33 \
  -H "Authorization: Bearer $KAIROS_API_KEY"

Response

200
{
  "id": "3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33",
  "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
  "agent_id": "assistant",
  "config_override": {},
  "enabled": true,
  "vertical_id": null,
  "allowed_origins": ["https://example.com"],
  "workforce_id": null,
  "base_agent_id": null
}
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/installations/3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Pause or resume an agent

PATCH/v1/admin/installations/{install_id}/enabled
200admin session

The kill switch. While an agent is disabled every new turn against it is refused with a 409, so this is what to call when an agent is answering badly and you want it to stop now rather than after a config change propagates. Existing transcripts are untouched.

Path parameters

NameTypeDescription
install_iduuid
required
The installation id.

Body

NameTypeDescription
enabledboolean
required
false pauses, true resumes.

Request

curl
curl -X PATCH https://api.trykairos.in/v1/admin/installations/3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33/enabled \
  -H "Authorization: Bearer $KAIROS_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled":false}'

Response

200
{
  "id": "3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33",
  "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
  "agent_id": "assistant",
  "config_override": {},
  "enabled": false,
  "vertical_id": null,
  "allowed_origins": ["https://example.com"],
  "workforce_id": null,
  "base_agent_id": null
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
PATCHhttps://api.trykairos.in/v1/admin/installations/3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33/enabled

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Set the origin allowlist

PUT/v1/admin/installations/{install_id}/allowed-origins
200admin session

Which websites may start a conversation with this agent. Replaces the list wholesale, up to 20 entries. An empty list means any origin, which is the back-compatible default and not what you want on a production agent.

Path parameters

NameTypeDescription
install_iduuid
required
The installation id.

Body

NameTypeDescription
allowed_originsstring[]
required
Up to 20 origins. An empty array clears the allowlist.

Each entry must be a bare origin: scheme://host with an optional port, no path, no credentials, no wildcard. Plain http is accepted only for localhost. Anything else is a 422. Entries are stored canonicalised (lowercased, default port stripped) and de-duplicated, so what you read back may differ in spelling from what you sent.

Request

curl
curl -X PUT https://api.trykairos.in/v1/admin/installations/3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33/allowed-origins \
  -H "Authorization: Bearer $KAIROS_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"allowed_origins":["https://example.com","https://www.example.com"]}'

Response

200
{
  "id": "3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33",
  "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
  "agent_id": "assistant",
  "config_override": {},
  "enabled": true,
  "vertical_id": null,
  "allowed_origins": ["https://example.com", "https://www.example.com"],
  "workforce_id": null,
  "base_agent_id": null
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
PUThttps://api.trykairos.in/v1/admin/installations/3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33/allowed-origins

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Chat

Start a conversation with an agent and stream its reply. This is the surface behind the web widget, and it is the one to use when you want an agent inside your own product rather than in ours.

Start a conversation

POST/v1/agent-installations/{installation_id}/conversations
201no api key

Opens a conversation against an agent and returns it. The id in the response is the handle for every message that follows. The agent's welcome line is stored as the first assistant message, so a transcript read back later starts where the customer's screen did.

Path parameters

NameTypeDescription
installation_iduuid
required
The agent installation to talk to. Read it from GET /v1/admin/installations.

Body

NameTypeDescription
channelstring
required
Which channel this conversation belongs to. Use "web" for a conversation you drive over HTTP.
external_user_idstringYour own id for the person, if you have one. It is what later resolves this conversation to a contact.

Send an Origin header that is on the install's allowlist, or the call is a 403. When the allowlist is empty any origin is accepted. An unknown installation id is a 404, and too many starts in a short window is a 429 with retry-after.

Request

curl
curl -X POST https://api.trykairos.in/v1/agent-installations/3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33/conversations \
  -H "Content-Type: application/json" \
  -H "Origin: https://example.com" \
  -d '{"channel":"web","external_user_id":"crm-8842"}'

Response

201
{
  "id": "9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611",
  "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
  "agent_installation_id": "3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33",
  "channel": "web",
  "external_user_id": "crm-8842",
  "status": "active",
  "short_code": "K7QF2",
  "team_id": null,
  "metadata": {},
  "total_input_tokens": 0,
  "total_output_tokens": 0,
  "tool_call_count": 0,
  "stt_seconds": 0.0,
  "tts_characters": 0,
  "started_at": "2026-08-01T07:41:00+00:00",
  "ended_at": null,
  "workforce_id": null
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
POSThttps://api.trykairos.in/v1/agent-installations/3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33/conversations

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Send a message and stream the reply

POST/v1/conversations/{conversation_id}/messages
200no api key

Posts one user turn and streams the agent's response back as Server-Sent Events. The response is not JSON: it is an event stream that stays open for the length of the turn, so read it incrementally rather than waiting for a body. Use curl -N, or an EventSource-style client.

Path parameters

NameTypeDescription
conversation_iduuid
required
From the start-a-conversation response.

Body

NameTypeDescription
textstring
required
The user's message.

Before a single token is spent the turn is refused if the agent is paused (409), the workspace is out of credits or over its budget (402), the conversation has passed its cost ceiling (402), or the tenant is sending too fast (429, with retry-after). When a human has taken the thread over in the console the message is still recorded but the agent does not answer: the stream carries one operator_active event and ends.

Request

curl
curl -N -X POST https://api.trykairos.in/v1/conversations/9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611/messages \
  -H "Content-Type: application/json" \
  -d '{"text":"What does the Growth plan include?"}'

Response

200
event: run_started
data: {"type":"run_started","thread_id":"9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611","agent_id":"assistant"}

event: assistant_token
data: {"type":"assistant_token","text":"The Growth plan"}

event: assistant_token
data: {"type":"assistant_token","text":" includes three agents"}

event: assistant_message
data: {"type":"assistant_message","text":"The Growth plan includes three agents and 5,000 messages a month.","input_tokens":812,"output_tokens":24,"cache_read_tokens":0,"cache_write_tokens":0}

event: run_completed
data: {"type":"run_completed","thread_id":"9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611"}

event: message_persisted
data: {"message_id":"e4d81b22-90fa-4c76-a3b0-11d2e3f45a67"}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
POSThttps://api.trykairos.in/v1/conversations/9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611/messages

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Conversations

Every exchange an agent has, on any channel, is a conversation. Messages, the cost and token roll-up, and the tool calls the agent made are all read from here.

List conversations

GET/v1/admin/conversations
200

Newest first, cursor-paginated. All filters are optional and combine with AND. Pass the next_cursor from a response back as cursor to get the following page; next_cursor is null on the last page.

Query parameters

NameTypeDescription
cursorstringOpaque cursor from the previous response.
limitintegerDefault 50.
agent_idstringRestrict to one agent.
statusstringOne of active, ended, errored, escalated.
channelstringOne of web, whatsapp, pstn, email.
started_afterstringISO-8601 instant.
started_beforestringISO-8601 instant.
sentimentstringClassifier sentiment label.
intentstringClassifier intent label.
team_iduuidRestrict to conversations assigned to one team.
qstringFree-text search.

Request

curl
curl -G https://api.trykairos.in/v1/admin/conversations \
  -H "Authorization: Bearer $KAIROS_API_KEY" \
  -d channel=whatsapp \
  -d status=ended \
  -d limit=20

Response

200
{
  "items": [
    {
      "id": "9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611",
      "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
      "agent_installation_id": "3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33",
      "channel": "whatsapp",
      "external_user_id": "+919876543210",
      "status": "ended",
      "short_code": "K7QF2",
      "team_id": null,
      "metadata": {},
      "total_input_tokens": 2841,
      "total_output_tokens": 613,
      "tool_call_count": 2,
      "stt_seconds": 0.0,
      "tts_characters": 0,
      "started_at": "2026-07-31T08:41:02+00:00",
      "ended_at": "2026-07-31T08:47:55+00:00",
      "agent_id": "assistant",
      "agent_display_name": "Front desk",
      "vertical_id": null,
      "sentiment": "positive",
      "intent": "pricing_enquiry",
      "topics": ["pricing"],
      "user_message_count": 6
    }
  ],
  "next_cursor": "2026-07-31T08:41:02+00:00",
  "available_agents": [{ "agent_id": "assistant", "display_name": "Front desk" }]
}
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/conversations?limit=20&status=ended&channel=whatsapp

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Retrieve a transcript

GET/v1/admin/conversations/{conversation_id}
200

The conversation record plus its full message list, oldest first. role is one of user, assistant, tool or system. An operator reply is stored with role assistant and metadata.author set to operator.

Path parameters

NameTypeDescription
conversation_iduuid
required
The conversation id.

Request

curl
curl https://api.trykairos.in/v1/admin/conversations/9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611 \
  -H "Authorization: Bearer $KAIROS_API_KEY"

Response

200
{
  "conversation": {
    "id": "9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611",
    "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
    "agent_installation_id": "3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33",
    "channel": "whatsapp",
    "external_user_id": "+919876543210",
    "status": "ended",
    "metadata": {},
    "total_input_tokens": 2841,
    "total_output_tokens": 613,
    "tool_call_count": 2,
    "started_at": "2026-07-31T08:41:02+00:00",
    "ended_at": "2026-07-31T08:47:55+00:00"
  },
  "messages": [
    {
      "id": "1a7c33b8-0d94-4e21-8f6a-55c2d0e9b477",
      "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
      "conversation_id": "9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611",
      "role": "user",
      "content": "What does the Growth plan include?",
      "tool_call_id": null,
      "tool_name": null,
      "metadata": {},
      "created_at": "2026-07-31T08:41:03+00:00"
    }
  ]
}
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/conversations/9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Retrieve a timeline

GET/v1/admin/conversations/{conversation_id}/timeline
200

The same conversation as an ordered event stream plus its aggregates. Each event kind is one of user_message, assistant_message, tool_call, tool_result, rag_retrieval or error, so this is where you see which tools ran and which knowledge chunks were retrieved.

Path parameters

NameTypeDescription
conversation_iduuid
required
The conversation id.

Request

curl
curl https://api.trykairos.in/v1/admin/conversations/9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611/timeline \
  -H "Authorization: Bearer $KAIROS_API_KEY"

Response

200
{
  "conversation": { "id": "9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611", "status": "ended" },
  "events": [
    {
      "kind": "user_message",
      "created_at": "2026-07-31T08:41:03+00:00",
      "text": "What does the Growth plan include?"
    },
    {
      "kind": "rag_retrieval",
      "created_at": "2026-07-31T08:41:04+00:00",
      "rag": {
        "query": "What does the Growth plan include?",
        "top_k": 4,
        "chunk_ids": ["5b1f0c77-2d43-4a90-8e11-c6d7e8f90a12"],
        "similarity_scores": [0.81],
        "latency_ms": 96
      }
    }
  ],
  "aggregates": {
    "total_input_tokens": 2841,
    "total_output_tokens": 613,
    "total_tokens": 3454,
    "tool_call_count": 2,
    "duration_ms": 413000,
    "status": "ended",
    "cost_usd": 0.0114,
    "cost_inr_paise": 96
  }
}
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/conversations/9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611/timeline

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Send a human reply

POST/v1/admin/conversations/{conversation_id}/operator-message
200

Append a human reply to a live conversation. Sending implies takeover: the agent stops answering that thread, so the customer never gets two replies to one question. The message is stored with role assistant and metadata.author set to operator.

Path parameters

NameTypeDescription
conversation_iduuid
required
The conversation id.

Body

NameTypeDescription
textstring
required
The reply. 1 to 4000 characters.

Request

curl
curl -X POST https://api.trykairos.in/v1/admin/conversations/9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611/operator-message \
  -H "Authorization: Bearer $KAIROS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text":"Taking this one myself. Growth includes 3 agents and 5,000 messages a month."}'

Response

200
{
  "id": "e4d81b22-90fa-4c76-a3b0-11d2e3f45a67",
  "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
  "conversation_id": "9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611",
  "role": "assistant",
  "content": "Taking this one myself. Growth includes 3 agents and 5,000 messages a month.",
  "tool_call_id": null,
  "tool_name": null,
  "metadata": { "author": "operator" },
  "created_at": "2026-07-31T08:44:10+00:00"
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
POSThttps://api.trykairos.in/v1/admin/conversations/9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611/operator-message

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Contacts

The people your agents have talked to, deduplicated across channels. A phone number on a call and an email address on a web chat resolve to the same contact.

List contacts

GET/v1/admin/contacts
200module: crm

Filter by free text, owner, lifecycle stage, or restrict to qualified leads.

Query parameters

NameTypeDescription
qstringFree-text search over name, phone and email.
owneruuidOperator the contact is assigned to.
stagestringLifecycle stage to filter by.
leads_onlybooleanDefault false.
limitintegerDefault 100.

Request

curl
curl -G https://api.trykairos.in/v1/admin/contacts \
  -H "Authorization: Bearer $KAIROS_API_KEY" \
  -d q=sharma \
  -d limit=25

Response

200
[
  {
    "id": "7d0e2f45-9a11-4b63-8c27-3e5f6a7b8c90",
    "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
    "full_name": "Anita Sharma",
    "primary_phone": "+919876543210",
    "primary_email": "anita@example.com",
    "attributes": { "interest": "growth-plan" },
    "owner_operator_id": null,
    "lifecycle_stage": "lead",
    "qualification_score": 0.72,
    "qualification_notes": null,
    "intent_signals": {},
    "lead_source": "whatsapp",
    "crm_push_status": null,
    "crm_push_error": null,
    "crm_external_id": null,
    "first_conversation_id": "9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611",
    "created_at": "2026-07-30T11:02:19+00:00",
    "updated_at": "2026-07-31T08:47:55+00:00"
  }
]
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/contacts?q=sharma&limit=25

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Resolve a contact by identifier

GET/v1/admin/contacts/by-identity
200module: crm

Look a person up by any identifier you already hold: an E.164 phone number, an email address, or an opaque external id. Returns 404 when nothing matches.

Query parameters

NameTypeDescription
valuestring
required
The raw identifier to resolve.

Request

curl
curl -G https://api.trykairos.in/v1/admin/contacts/by-identity \
  -H "Authorization: Bearer $KAIROS_API_KEY" \
  --data-urlencode "value=+919876543210"

Response

200
{
  "id": "7d0e2f45-9a11-4b63-8c27-3e5f6a7b8c90",
  "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
  "full_name": "Anita Sharma",
  "primary_phone": "+919876543210",
  "primary_email": "anita@example.com",
  "lifecycle_stage": "lead",
  "created_at": "2026-07-30T11:02:19+00:00",
  "updated_at": "2026-07-31T08:47:55+00:00"
}
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/contacts/by-identity?value=%2B919876543210

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Retrieve a contact

GET/v1/admin/contacts/{contact_id}
200module: crm

The contact, every identity that resolves to it, and its lead records. identities[].kind tells you which channel each identifier came from.

Path parameters

NameTypeDescription
contact_iduuid
required
The contact id.

Request

curl
curl https://api.trykairos.in/v1/admin/contacts/7d0e2f45-9a11-4b63-8c27-3e5f6a7b8c90 \
  -H "Authorization: Bearer $KAIROS_API_KEY"

Response

200
{
  "contact": {
    "id": "7d0e2f45-9a11-4b63-8c27-3e5f6a7b8c90",
    "full_name": "Anita Sharma",
    "primary_phone": "+919876543210",
    "primary_email": "anita@example.com",
    "lifecycle_stage": "lead"
  },
  "identities": [
    {
      "id": "c1b2a3d4-e5f6-4708-9a1b-2c3d4e5f6071",
      "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
      "contact_id": "7d0e2f45-9a11-4b63-8c27-3e5f6a7b8c90",
      "kind": "phone",
      "value": "+919876543210",
      "created_at": "2026-07-30T11:02:19+00:00"
    }
  ],
  "lead_ids": ["a9f8e7d6-c5b4-4a32-9180-7f6e5d4c3b2a"],
  "leads": [
    {
      "id": "a9f8e7d6-c5b4-4a32-9180-7f6e5d4c3b2a",
      "stage": "qualified",
      "qualification_score": "0.72",
      "conversation_id": "9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611",
      "created_at": "2026-07-30T11:02:19+00:00"
    }
  ]
}
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/contacts/7d0e2f45-9a11-4b63-8c27-3e5f6a7b8c90

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Create a contact

POST/v1/admin/contacts
201module: crmadmin session

Create a person by hand. At least one of email or phone should be supplied, otherwise nothing can later resolve to this contact.

Body

NameTypeDescription
full_namestringDisplay name.
emailstringBecomes an email identity.
phonestringE.164. Becomes a phone identity.
attributesobjectFree-form string map. `interest` is the canonical key for what the person is asking about.

Request

curl
curl -X POST https://api.trykairos.in/v1/admin/contacts \
  -H "Authorization: Bearer $KAIROS_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "full_name": "Anita Sharma",
    "phone": "+919876543210",
    "email": "anita@example.com",
    "attributes": { "interest": "growth-plan" }
  }'

Response

201
{
  "id": "7d0e2f45-9a11-4b63-8c27-3e5f6a7b8c90",
  "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
  "full_name": "Anita Sharma",
  "primary_phone": "+919876543210",
  "primary_email": "anita@example.com",
  "attributes": { "interest": "growth-plan" },
  "lifecycle_stage": "contact",
  "created_at": "2026-08-01T06:20:11+00:00",
  "updated_at": "2026-08-01T06:20:11+00:00"
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
POSThttps://api.trykairos.in/v1/admin/contacts

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Update a contact

PATCH/v1/admin/contacts/{contact_id}
200module: crmadmin session

Partial update. Omitted fields are left alone.

Path parameters

NameTypeDescription
contact_iduuid
required
The contact id.

Body

NameTypeDescription
full_namestringDisplay name.
emailstringReplaces the primary email.
phonestringReplaces the primary phone.
attributesobjectFree-form map; merged into the existing one.

Request

curl
curl -X PATCH https://api.trykairos.in/v1/admin/contacts/7d0e2f45-9a11-4b63-8c27-3e5f6a7b8c90 \
  -H "Authorization: Bearer $KAIROS_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"full_name":"Anita Sharma","attributes":{"interest":"scale-plan"}}'

Response

200
{
  "id": "7d0e2f45-9a11-4b63-8c27-3e5f6a7b8c90",
  "full_name": "Anita Sharma",
  "attributes": { "interest": "scale-plan" },
  "lifecycle_stage": "lead",
  "updated_at": "2026-08-01T06:31:44+00:00"
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
PATCHhttps://api.trykairos.in/v1/admin/contacts/7d0e2f45-9a11-4b63-8c27-3e5f6a7b8c90

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Companies and deals

The sales pipeline. Companies are organisations; deals move through the stages your workspace defines and carry an amount in paise.

List companies

GET/v1/admin/crm/companies
200module: crm

Every company in the workspace, optionally filtered by a name search.

Query parameters

NameTypeDescription
qstringFree-text search over the company name.

Request

curl
curl -G https://api.trykairos.in/v1/admin/crm/companies \
  -H "Authorization: Bearer $KAIROS_API_KEY" \
  -d q=acme

Response

200
{
  "companies": [
    {
      "id": "2e9c4b71-8d05-4f36-a2c8-6b7d8e9f0a12",
      "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
      "name": "Acme Industries",
      "domain": "acme.example",
      "industry": "manufacturing",
      "gst_number": null,
      "notes": null,
      "owner_user_id": null,
      "created_at": "2026-07-12T05:14:00+00:00",
      "updated_at": "2026-07-12T05:14:00+00:00"
    }
  ]
}
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/crm/companies?q=acme

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Create a company

POST/v1/admin/crm/companies
201module: crm

name is the only required field.

Body

NameTypeDescription
namestring
required
1 to 200 characters.
domainstringPrimary web domain.
industrystringFree text.
gst_numberstringGSTIN, if you hold one.
notesstringFree text.

Request

curl
curl -X POST https://api.trykairos.in/v1/admin/crm/companies \
  -H "Authorization: Bearer $KAIROS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Acme Industries","domain":"acme.example","industry":"manufacturing"}'

Response

201
{
  "id": "2e9c4b71-8d05-4f36-a2c8-6b7d8e9f0a12",
  "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
  "name": "Acme Industries",
  "domain": "acme.example",
  "industry": "manufacturing",
  "gst_number": null,
  "notes": null,
  "owner_user_id": null,
  "created_at": "2026-08-01T06:40:02+00:00",
  "updated_at": "2026-08-01T06:40:02+00:00"
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
POSThttps://api.trykairos.in/v1/admin/crm/companies

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

List deals

GET/v1/admin/crm/deals
200module: crm

The whole pipeline in one call: the stage order your workspace uses, the deals themselves, and a per-stage count and value summary.

Query parameters

NameTypeDescription
stagestringRestrict to one stage.
company_iduuidRestrict to one company.

Request

curl
curl -G https://api.trykairos.in/v1/admin/crm/deals \
  -H "Authorization: Bearer $KAIROS_API_KEY" \
  -d stage=proposal

Response

200
{
  "stages": ["new", "qualified", "proposal", "won", "lost"],
  "deals": [
    {
      "id": "8b7a6c5d-4e3f-4021-9a8b-7c6d5e4f3a21",
      "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
      "title": "Acme rollout",
      "company_id": "2e9c4b71-8d05-4f36-a2c8-6b7d8e9f0a12",
      "contact_id": "7d0e2f45-9a11-4b63-8c27-3e5f6a7b8c90",
      "stage": "proposal",
      "amount_paise": 19999900,
      "expected_close_date": "2026-08-30",
      "owner_user_id": null,
      "source": "whatsapp",
      "notes": null,
      "position": 1.0,
      "closed_at": null,
      "invoice_ref": null,
      "invoiced_at": null,
      "created_at": "2026-07-20T09:00:00+00:00",
      "updated_at": "2026-07-28T12:11:03+00:00",
      "company_name": "Acme Industries",
      "contact_name": "Anita Sharma"
    }
  ],
  "summary": { "proposal": { "count": 1, "amount_paise": 19999900 } }
}
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/crm/deals?stage=proposal

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Create a deal

POST/v1/admin/crm/deals
201module: crm

amount_paise is an integer in paise, never rupees: 19999900 is Rs 1,99,999. Keeping money in the smallest unit is the only way to avoid float drift on a total.

Body

NameTypeDescription
titlestring
required
1 to 200 characters.
stagestringDefaults to "new".
amount_paiseintegerDefaults to 0. Must be 0 or more.
company_iduuidCompany the deal belongs to.
contact_iduuidPerson the deal belongs to.
expected_close_datedateYYYY-MM-DD.
sourcestringFree text.
notesstringFree text.

Request

curl
curl -X POST https://api.trykairos.in/v1/admin/crm/deals \
  -H "Authorization: Bearer $KAIROS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Acme rollout",
    "stage": "proposal",
    "amount_paise": 19999900,
    "company_id": "2e9c4b71-8d05-4f36-a2c8-6b7d8e9f0a12",
    "expected_close_date": "2026-08-30"
  }'

Response

201
{
  "id": "8b7a6c5d-4e3f-4021-9a8b-7c6d5e4f3a21",
  "title": "Acme rollout",
  "stage": "proposal",
  "amount_paise": 19999900,
  "company_id": "2e9c4b71-8d05-4f36-a2c8-6b7d8e9f0a12",
  "contact_id": null,
  "expected_close_date": "2026-08-30",
  "position": 1.0,
  "created_at": "2026-08-01T06:44:19+00:00",
  "updated_at": "2026-08-01T06:44:19+00:00"
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
POSThttps://api.trykairos.in/v1/admin/crm/deals

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Move a deal

PATCH/v1/admin/crm/deals/{deal_id}/stage
200module: crm

Move a deal to another stage, optionally at a given position within it. This is the endpoint a board drag-and-drop calls.

Path parameters

NameTypeDescription
deal_iduuid
required
The deal id.

Body

NameTypeDescription
stagestring
required
Target stage.
positionnumberSort position within the stage. Omit to append.

Request

curl
curl -X PATCH https://api.trykairos.in/v1/admin/crm/deals/8b7a6c5d-4e3f-4021-9a8b-7c6d5e4f3a21/stage \
  -H "Authorization: Bearer $KAIROS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"stage":"won","position":0}'

Response

200
{
  "id": "8b7a6c5d-4e3f-4021-9a8b-7c6d5e4f3a21",
  "title": "Acme rollout",
  "stage": "won",
  "amount_paise": 19999900,
  "position": 0.0,
  "closed_at": "2026-08-01T06:47:30+00:00",
  "updated_at": "2026-08-01T06:47:30+00:00"
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
PATCHhttps://api.trykairos.in/v1/admin/crm/deals/8b7a6c5d-4e3f-4021-9a8b-7c6d5e4f3a21/stage

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Tickets

Work an agent decided a human or another agent should finish: a callback to place, a message to send, an action waiting on approval.

List tickets

GET/v1/admin/tickets
200module: support

Offset-paginated, with a counts map alongside so you can badge a queue without a second call.

Query parameters

NameTypeDescription
statusstringOne of open, in_progress, waiting, done, cancelled.
ticket_typestringOne of followup_call, send_message, human_review, approval, other.
assignee_kindstringai or human.
assignee_user_idstringRestrict to one assignee.
team_iduuidRestrict to one team.
conversation_iduuidTickets raised from one conversation.
due_beforedate-timeOnly tickets due at or before this instant, soonest first.
counts_due_beforedate-timeAdds a `due` key to counts: open and in_progress tickets due by this instant.
statusesstringComma-separated statuses, for a folder spanning several.
limitintegerDefault 50.
offsetintegerDefault 0.

Request

curl
curl -G https://api.trykairos.in/v1/admin/tickets \
  -H "Authorization: Bearer $KAIROS_API_KEY" \
  -d status=open \
  -d limit=20

Response

200
{
  "tickets": [
    {
      "id": "6f5e4d3c-2b1a-4098-8765-4321fedcba09",
      "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
      "conversation_id": "9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611",
      "short_code": "T-1042",
      "title": "Call back about the Growth plan",
      "detail": "Asked for a quote including GST.",
      "ticket_type": "followup_call",
      "status": "open",
      "risk_level": null,
      "action_kind": null,
      "assignee_kind": "human",
      "assignee_agent_install_id": null,
      "assignee_user_id": null,
      "team_id": null,
      "due_at": "2026-08-02T05:30:00+00:00",
      "source": "disposition",
      "contact_identifier": "+919876543210",
      "display_name": "Anita Sharma",
      "attempts": 0,
      "payload": {},
      "created_at": "2026-07-31T08:48:02+00:00",
      "updated_at": "2026-07-31T08:48:02+00:00"
    }
  ],
  "counts": { "open": 1, "in_progress": 0, "waiting": 0, "done": 12, "cancelled": 0 }
}
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/tickets?status=open&limit=20

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Create a ticket

POST/v1/admin/tickets
201module: support

Raise work by hand. source is always manual on this route; disposition and escalation are written by the platform only.

Body

NameTypeDescription
titlestring
required
1 to 300 characters.
ticket_typestringOne of followup_call, send_message, human_review, approval, other. Defaults to "other".
detailstringFree text.
conversation_iduuidConversation this relates to.
assignee_kindstringai or human.
assignee_agent_install_iduuidAgent to assign to, when assignee_kind is ai.
assignee_user_idstringPerson to assign to.
team_iduuidTeam to assign to.
due_atdate-timeISO-8601 instant.
contact_identifierstringPhone or email the work concerns.

Request

curl
curl -X POST https://api.trykairos.in/v1/admin/tickets \
  -H "Authorization: Bearer $KAIROS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Call back about the Growth plan",
    "ticket_type": "followup_call",
    "detail": "Asked for a quote including GST.",
    "contact_identifier": "+919876543210",
    "due_at": "2026-08-02T05:30:00Z"
  }'

Response

201
{
  "id": "6f5e4d3c-2b1a-4098-8765-4321fedcba09",
  "title": "Call back about the Growth plan",
  "ticket_type": "followup_call",
  "status": "open",
  "source": "manual",
  "contact_identifier": "+919876543210",
  "due_at": "2026-08-02T05:30:00+00:00",
  "attempts": 0,
  "created_at": "2026-08-01T06:52:40+00:00",
  "updated_at": "2026-08-01T06:52:40+00:00"
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
POSThttps://api.trykairos.in/v1/admin/tickets

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Update a ticket

PATCH/v1/admin/tickets/{ticket_id}
200module: support

Partial update. A ticket id from another workspace returns 404, the same as an unknown id.

Path parameters

NameTypeDescription
ticket_iduuid
required
The ticket id.

Body

NameTypeDescription
statusstringOne of open, in_progress, waiting, done, cancelled.
titlestringNew title.
detailstringNew detail.
due_atdate-timeNew due instant.
team_iduuidReassign to a team.
draftstringDraft reply held against the ticket.

Request

curl
curl -X PATCH https://api.trykairos.in/v1/admin/tickets/6f5e4d3c-2b1a-4098-8765-4321fedcba09 \
  -H "Authorization: Bearer $KAIROS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"done"}'

Response

200
{
  "id": "6f5e4d3c-2b1a-4098-8765-4321fedcba09",
  "title": "Call back about the Growth plan",
  "status": "done",
  "updated_at": "2026-08-01T07:03:12+00:00"
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
PATCHhttps://api.trykairos.in/v1/admin/tickets/6f5e4d3c-2b1a-4098-8765-4321fedcba09

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Knowledge

The documents your agents answer from. Upload is asynchronous: the file is stored and queued, and you poll ingestion_status until it reads ready.

List documents

GET/v1/admin/documents
200

ingestion_status is one of pending, processing, ready or errored. Only ready documents are retrievable by an agent; errored ones carry the reason in ingestion_error.

Request

curl
curl https://api.trykairos.in/v1/admin/documents \
  -H "Authorization: Bearer $KAIROS_API_KEY"

Response

200
[
  {
    "id": "5b1f0c77-2d43-4a90-8e11-c6d7e8f90a12",
    "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
    "agent_installation_id": null,
    "agent_installation_ids": [],
    "filename": "pricing.pdf",
    "mime_type": "application/pdf",
    "byte_size": 184320,
    "object_key": "tenants/b0a4e6d2/documents/5b1f0c77.pdf",
    "sha256": "9f2c1d...",
    "ingestion_status": "ready",
    "ingestion_error": null,
    "chunk_count": 42,
    "ingestion_started_at": "2026-07-29T10:01:02+00:00",
    "ingestion_finished_at": "2026-07-29T10:01:38+00:00",
    "uploaded_at": "2026-07-29T10:01:00+00:00",
    "updated_at": "2026-07-29T10:01:38+00:00"
  }
]
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/documents

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Upload a document

POST/v1/admin/documents
202admin session

Multipart upload. Returns 202 with a pending document; ingestion runs in the background. Re-uploading identical bytes returns the existing document rather than a duplicate. An unsupported file type is 415, and a file over the size limit is 413.

Form fields (multipart/form-data)

NameTypeDescription
filefile
required
The document to ingest.
agent_installation_iduuid (repeatable)Agents this document is shared with. Empty means all of them. An id that is not an installation in your workspace is a 400.

agent_installation_id is a REPEATED form field. Omit it entirely to share the document with every agent in the workspace; repeat it once per agent to share with just those.

Request

curl
curl -X POST https://api.trykairos.in/v1/admin/documents \
  -H "Authorization: Bearer $KAIROS_ADMIN_TOKEN" \
  -F "file=@pricing.pdf" \
  -F "agent_installation_id=3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33"

Response

202
{
  "id": "5b1f0c77-2d43-4a90-8e11-c6d7e8f90a12",
  "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
  "agent_installation_ids": ["3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33"],
  "filename": "pricing.pdf",
  "mime_type": "application/pdf",
  "byte_size": 184320,
  "ingestion_status": "pending",
  "ingestion_error": null,
  "chunk_count": 0,
  "uploaded_at": "2026-08-01T07:10:44+00:00",
  "updated_at": "2026-08-01T07:10:44+00:00"
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.

This endpoint takes a multipart upload, which is a file rather than a body you can type. Run it in sample mode to see the response, and send the real thing with the curl above.

POSThttps://api.trykairos.in/v1/admin/documents

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Test retrieval

POST/v1/admin/documents/test-retrieval
200

Ask what the agent would retrieve for a question, and what it would answer from it. This is the endpoint to reach for when an agent gives a wrong answer: it shows the exact passages behind the reply. It is a probe, so it does not write a conversation event.

Body

NameTypeDescription
querystring
required
1 to 500 characters.
top_kintegerChunks to retrieve. 1 to 10, default 4.

Request

curl
curl -X POST https://api.trykairos.in/v1/admin/documents/test-retrieval \
  -H "Authorization: Bearer $KAIROS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"What is included in the Growth plan?","top_k":4}'

Response

200
{
  "query": "What is included in the Growth plan?",
  "answer": "The Growth plan includes three agents and five thousand messages a month.",
  "chunks": [
    {
      "chunk_id": "d4c3b2a1-0f9e-4d8c-b7a6-5f4e3d2c1b0a",
      "document_id": "5b1f0c77-2d43-4a90-8e11-c6d7e8f90a12",
      "similarity": 0.81,
      "content": "Growth: 3 agents, 5,000 messages/month, voice add-on available."
    }
  ]
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
POSThttps://api.trykairos.in/v1/admin/documents/test-retrieval

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Delete a document

DELETE/v1/admin/documents/{document_id}
204admin session

Removes the document and every chunk indexed from it. No response body.

Path parameters

NameTypeDescription
document_iduuid
required
The document id.

Request

curl
curl -X DELETE https://api.trykairos.in/v1/admin/documents/5b1f0c77-2d43-4a90-8e11-c6d7e8f90a12 \
  -H "Authorization: Bearer $KAIROS_ADMIN_TOKEN"

Response

204
204 No Content
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
DELETEhttps://api.trykairos.in/v1/admin/documents/5b1f0c77-2d43-4a90-8e11-c6d7e8f90a12

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Webhooks

Rather than polling, subscribe an https endpoint and we POST to it when something happens. Every delivery is signed.

List subscriptions

GET/v1/admin/webhooks
200

Includes the signing secret, because you need it to verify deliveries. consecutive_failures resets to 0 on any 2xx; when it reaches 10 the subscription is disabled and auto_disabled_at is set.

Request

curl
curl https://api.trykairos.in/v1/admin/webhooks \
  -H "Authorization: Bearer $KAIROS_API_KEY"

Response

200
[
  {
    "id": "aa11bb22-cc33-4d44-9e55-ff6600771188",
    "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
    "label": "Ops relay",
    "url": "https://hooks.example.com/kairos",
    "events": ["conversation.ended", "lead.qualified"],
    "secret": "u7Kx...",
    "enabled": true,
    "created_at": "2026-07-18T04:22:10+00:00",
    "consecutive_failures": 0,
    "last_success_at": "2026-07-31T08:47:56+00:00",
    "auto_disabled_at": null
  }
]
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/webhooks

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

List event names

GET/v1/admin/webhooks/events
200

The event names a subscription may ask for, sorted. Anything not in this list is dropped at creation rather than stored as a typo that never fires.

Request

curl
curl https://api.trykairos.in/v1/admin/webhooks/events \
  -H "Authorization: Bearer $KAIROS_API_KEY"

Response

200
[
  "budget.alert",
  "budget.exceeded",
  "call.answered",
  "call.ended",
  "call.escalated",
  "call.received",
  "conversation.classified",
  "conversation.ended",
  "conversation.started",
  "feedback.received",
  "lead.qualified"
]
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/webhooks/events

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Create a subscription

POST/v1/admin/webhooks
201admin session

The url must be public https. Plain http, and any host that resolves to a private, loopback or link-local address, is refused at creation. An empty events list subscribes to nothing, so name the events you want.

Body

NameTypeDescription
labelstring
required
1 to 100 characters.
urlstring
required
Public https endpoint, up to 2000 characters.
eventsstring[]Event names from GET /v1/admin/webhooks/events. Unknown names are dropped.

Request

curl
curl -X POST https://api.trykairos.in/v1/admin/webhooks \
  -H "Authorization: Bearer $KAIROS_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Ops relay",
    "url": "https://hooks.example.com/kairos",
    "events": ["conversation.ended", "lead.qualified"]
  }'

Response

201
{
  "id": "aa11bb22-cc33-4d44-9e55-ff6600771188",
  "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
  "label": "Ops relay",
  "url": "https://hooks.example.com/kairos",
  "events": ["conversation.ended", "lead.qualified"],
  "secret": "u7Kx...",
  "enabled": true,
  "created_at": "2026-08-01T07:20:00+00:00",
  "consecutive_failures": 0,
  "last_success_at": null,
  "auto_disabled_at": null
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
POSThttps://api.trykairos.in/v1/admin/webhooks

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Enable or disable a subscription

PATCH/v1/admin/webhooks/{sub_id}
200admin session

enabled is the only patchable field. Sending a body without it is a 400 rather than a silent no-op. This is also how you bring back a subscription that auto-disabled after repeated failures.

Path parameters

NameTypeDescription
sub_iduuid
required
The subscription id.

Body

NameTypeDescription
enabledboolean
required
Whether to deliver.

Request

curl
curl -X PATCH https://api.trykairos.in/v1/admin/webhooks/aa11bb22-cc33-4d44-9e55-ff6600771188 \
  -H "Authorization: Bearer $KAIROS_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled":false}'

Response

200
{
  "id": "aa11bb22-cc33-4d44-9e55-ff6600771188",
  "label": "Ops relay",
  "url": "https://hooks.example.com/kairos",
  "events": ["conversation.ended", "lead.qualified"],
  "enabled": false,
  "consecutive_failures": 0
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
PATCHhttps://api.trykairos.in/v1/admin/webhooks/aa11bb22-cc33-4d44-9e55-ff6600771188

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

List recent delivery attempts

GET/v1/admin/webhooks/{sub_id}/attempts
200

What we sent and what came back. status_code is null when the request never completed, and error then carries why.

Path parameters

NameTypeDescription
sub_iduuid
required
The subscription id.

Request

curl
curl https://api.trykairos.in/v1/admin/webhooks/aa11bb22-cc33-4d44-9e55-ff6600771188/attempts \
  -H "Authorization: Bearer $KAIROS_API_KEY"

Response

200
[
  {
    "id": "11223344-5566-4778-899a-bbccddeeff00",
    "subscription_id": "aa11bb22-cc33-4d44-9e55-ff6600771188",
    "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
    "event_name": "conversation.ended",
    "status_code": 200,
    "error": null,
    "attempted_at": "2026-07-31T08:47:56+00:00"
  }
]
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/webhooks/aa11bb22-cc33-4d44-9e55-ff6600771188/attempts

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Delete a subscription

DELETE/v1/admin/webhooks/{sub_id}
204admin session

Removes the subscription. No response body.

Path parameters

NameTypeDescription
sub_iduuid
required
The subscription id.

Request

curl
curl -X DELETE https://api.trykairos.in/v1/admin/webhooks/aa11bb22-cc33-4d44-9e55-ff6600771188 \
  -H "Authorization: Bearer $KAIROS_ADMIN_TOKEN"

Response

204
204 No Content
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
DELETEhttps://api.trykairos.in/v1/admin/webhooks/aa11bb22-cc33-4d44-9e55-ff6600771188

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Wallet

Usage is prepaid. Every balance and amount on this API is an integer in paise, so 500000 is Rs 5,000.

Retrieve the wallet

GET/v1/admin/wallet
200

Balance, status and auto-recharge settings. status is one of active, suspended or closed; an agent stops working when the wallet is not active.

Request

curl
curl https://api.trykairos.in/v1/admin/wallet \
  -H "Authorization: Bearer $KAIROS_API_KEY"

Response

200
{
  "id": "cc99dd88-ee77-4f66-a555-b444c333d222",
  "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
  "balance_paise": 482350,
  "credit_limit_paise": 0,
  "status": "active",
  "auto_recharge_enabled": true,
  "auto_recharge_threshold_paise": 100000,
  "auto_recharge_amount_paise": 500000,
  "razorpay_customer_id": "cust_ABC123",
  "razorpay_mandate_id": null,
  "trial_credit_paise": 0,
  "trial_expires_at": null,
  "created_at": "2026-06-02T04:00:00+00:00",
  "updated_at": "2026-07-31T08:48:00+00:00"
}
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/wallet

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Retrieve usage by SKU

GET/v1/admin/wallet/usage
200

Spend grouped by SKU over a trailing window, read from the wallet ledger. This is the authoritative record of what you were charged, not an estimate.

Query parameters

NameTypeDescription
rangeintegerDays to cover. Default 30.

Request

curl
curl -G https://api.trykairos.in/v1/admin/wallet/usage \
  -H "Authorization: Bearer $KAIROS_API_KEY" \
  -d range=7

Response

200
{
  "range_days": 7,
  "total_paise": 17650,
  "by_sku": [
    {
      "sku": "voice_minutes",
      "description": "Voice minutes",
      "debit_paise": 12000,
      "quantity": 20.0
    },
    {
      "sku": "wa_messages",
      "description": "WhatsApp messages",
      "debit_paise": 5650,
      "quantity": 113.0
    }
  ]
}
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/wallet/usage?range=7

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

API keys

Keys are shown once, at creation, and stored only as a hash. There is no way to read a key back; if you lose one, revoke it and make another.

List keys

GET/v1/admin/api-keys
200

Metadata only: label, prefix, and when the key was last used. The key itself is never returned by a read.

Request

curl
curl https://api.trykairos.in/v1/admin/api-keys \
  -H "Authorization: Bearer $KAIROS_API_KEY"

Response

200
[
  {
    "id": "ab12cd34-ef56-4789-a0b1-c2d3e4f5a6b7",
    "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
    "label": "billing-sync",
    "key_prefix": "kk_7Qx2",
    "last_used_at": "2026-07-31T08:40:00+00:00",
    "revoked_at": null,
    "created_at": "2026-07-01T05:00:00+00:00"
  }
]
Try itSample response, or a live call with your key
GEThttps://api.trykairos.in/v1/admin/api-keys

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Create a key

POST/v1/admin/api-keys
201admin session

cleartext is returned exactly once, in this response. Store it before you close the connection.

Body

NameTypeDescription
labelstring
required
1 to 100 characters. Name it after what will hold it.

Request

curl
curl -X POST https://api.trykairos.in/v1/admin/api-keys \
  -H "Authorization: Bearer $KAIROS_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"label":"billing-sync"}'

Response

201
{
  "key": {
    "id": "ab12cd34-ef56-4789-a0b1-c2d3e4f5a6b7",
    "tenant_id": "b0a4e6d2-1f38-4c5a-9d70-2a1b3c4d5e6f",
    "label": "billing-sync",
    "key_prefix": "kk_7Qx2",
    "last_used_at": null,
    "revoked_at": null,
    "created_at": "2026-08-01T07:30:00+00:00"
  },
  "cleartext": "kk_7Qx2..."
}
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
POSThttps://api.trykairos.in/v1/admin/api-keys

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Revoke a key

DELETE/v1/admin/api-keys/{key_id}
204admin session

Takes effect immediately. An already-revoked or unknown id is a 404. No response body.

Path parameters

NameTypeDescription
key_iduuid
required
The key id.

Request

curl
curl -X DELETE https://api.trykairos.in/v1/admin/api-keys/ab12cd34-ef56-4789-a0b1-c2d3e4f5a6b7 \
  -H "Authorization: Bearer $KAIROS_ADMIN_TOKEN"

Response

204
204 No Content
Try itSample response
Live calls are offered on read-only endpoints only, so a docs page can never write to your workspace.
DELETEhttps://api.trykairos.in/v1/admin/api-keys/ab12cd34-ef56-4789-a0b1-c2d3e4f5a6b7

Sample mode returns the documented example response for this endpoint. No request is made and no key is needed, so the values above do not change what comes back.

Something missing?

This reference covers the endpoints we consider stable and safe to build on. The platform has more surface than this, and if the call you need is not here, ask: email hello@trykairos.in with what you are trying to do. If something on this page is wrong, that is a bug and we want to hear about it.

For product setup rather than the API, see the platform guide.