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
- 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.
- Make a call. Requests go to
https://api.trykairos.inand 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 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.
# 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_.
Authorization: Bearer kk_your_key_hereA 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.
| Field | Type | Description |
|---|---|---|
| error | string | A stable machine code in snake_case. This is the field to branch on. It does not change once published. |
| message | string | One human sentence, safe to show a person. For a validation failure it names the offending fields. |
| request_id | string | null | The 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. |
| details | object | array | null | Structured 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.
{
"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
| error | Status | Meaning |
|---|---|---|
| unauthenticated | 401 | No credential, or a key that does not resolve. Check the Authorization header. |
| forbidden | 403 | Authenticated, but not allowed. Most often an API key attempting an action that needs a tenant-admin session. |
| module_locked | 403 | The business module behind this endpoint is not active on your plan. `details.module` names it. |
| not_found | 404 | No such record in your workspace. A record belonging to another workspace is indistinguishable from one that does not exist, on purpose. |
| conflict | 409 | The request contradicts the current state of the record. The message says how. |
| validation_failed | 422 | The body failed validation. `message` names the first offending fields; `details` is the per-field array. |
| rate_limited | 429 | Too many requests. See Rate limits below. |
| bad_request | 400 | The request was understood but is not usable as sent. |
| server_error | 5xx | A fault on our side. The message is deliberately generic; quote `request_id` to support. |
| unavailable | 503 | A 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.
{
"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.
| Event | data fields | Meaning |
|---|---|---|
| run_started | thread_id, agent_id | The turn began. thread_id is the conversation id. |
| assistant_token | text | One fragment of the reply as it is generated. Append them in order to render the answer live. |
| assistant_message | text, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens | A 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_started | tool, arguments, call_id | The agent invoked a tool. |
| tool_call_completed | tool, call_id, result, error | The tool returned. error is null on success. |
| run_completed | thread_id | The turn finished normally. |
| run_errored | thread_id, error | The 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_persisted | message_id | The id the assistant reply was stored under. Sent last, and only when the agent produced text. |
| operator_active | message | A 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_exceeded | reason, tokens, tool_calls | The conversation hit its cost ceiling part-way through and the run was halted. |
| server_shutting_down | reason | The 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.
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.
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
Every agent installed in your workspace, with the last heartbeat seen from each. Pass archived=true to get only archived agents instead.
Query parameters
| Name | Type | Description |
|---|---|---|
| archived | boolean | Default false. When true the response contains ONLY archived agents, newest-archived first. |
Request
curl https://api.trykairos.in/v1/admin/installations \
-H "Authorization: Bearer $KAIROS_API_KEY"Response
[
{
"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
https://api.trykairos.in/v1/admin/installationsSample 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
One installation. Returns 404 for an id that belongs to another workspace, the same as for an id that does not exist.
Path parameters
| Name | Type | Description |
|---|---|---|
| install_id | uuid required | The installation id. |
Request
curl https://api.trykairos.in/v1/admin/installations/3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33 \
-H "Authorization: Bearer $KAIROS_API_KEY"Response
{
"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
https://api.trykairos.in/v1/admin/installations/3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33Sample 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
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
| Name | Type | Description |
|---|---|---|
| install_id | uuid required | The installation id. |
Body
| Name | Type | Description |
|---|---|---|
| enabled | boolean required | false pauses, true resumes. |
Request
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
{
"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
https://api.trykairos.in/v1/admin/installations/3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33/enabledSample 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
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
| Name | Type | Description |
|---|---|---|
| install_id | uuid required | The installation id. |
Body
| Name | Type | Description |
|---|---|---|
| allowed_origins | string[] 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 -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
{
"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
https://api.trykairos.in/v1/admin/installations/3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33/allowed-originsSample 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
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
| Name | Type | Description |
|---|---|---|
| installation_id | uuid required | The agent installation to talk to. Read it from GET /v1/admin/installations. |
Body
| Name | Type | Description |
|---|---|---|
| channel | string required | Which channel this conversation belongs to. Use "web" for a conversation you drive over HTTP. |
| external_user_id | string | Your 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 -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
{
"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
https://api.trykairos.in/v1/agent-installations/3f1d9c0a-5d2e-4a7b-9c11-8e6f0a2b4c33/conversationsSample 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
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
| Name | Type | Description |
|---|---|---|
| conversation_id | uuid required | From the start-a-conversation response. |
Body
| Name | Type | Description |
|---|---|---|
| text | string 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 -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
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
https://api.trykairos.in/v1/conversations/9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611/messagesSample 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
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
| Name | Type | Description |
|---|---|---|
| cursor | string | Opaque cursor from the previous response. |
| limit | integer | Default 50. |
| agent_id | string | Restrict to one agent. |
| status | string | One of active, ended, errored, escalated. |
| channel | string | One of web, whatsapp, pstn, email. |
| started_after | string | ISO-8601 instant. |
| started_before | string | ISO-8601 instant. |
| sentiment | string | Classifier sentiment label. |
| intent | string | Classifier intent label. |
| team_id | uuid | Restrict to conversations assigned to one team. |
| q | string | Free-text search. |
Request
curl -G https://api.trykairos.in/v1/admin/conversations \
-H "Authorization: Bearer $KAIROS_API_KEY" \
-d channel=whatsapp \
-d status=ended \
-d limit=20Response
{
"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
https://api.trykairos.in/v1/admin/conversations?limit=20&status=ended&channel=whatsappSample 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
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
| Name | Type | Description |
|---|---|---|
| conversation_id | uuid required | The conversation id. |
Request
curl https://api.trykairos.in/v1/admin/conversations/9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611 \
-H "Authorization: Bearer $KAIROS_API_KEY"Response
{
"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
https://api.trykairos.in/v1/admin/conversations/9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611Sample 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
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
| Name | Type | Description |
|---|---|---|
| conversation_id | uuid required | The conversation id. |
Request
curl https://api.trykairos.in/v1/admin/conversations/9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611/timeline \
-H "Authorization: Bearer $KAIROS_API_KEY"Response
{
"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
https://api.trykairos.in/v1/admin/conversations/9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611/timelineSample 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
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
| Name | Type | Description |
|---|---|---|
| conversation_id | uuid required | The conversation id. |
Body
| Name | Type | Description |
|---|---|---|
| text | string required | The reply. 1 to 4000 characters. |
Request
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
{
"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
https://api.trykairos.in/v1/admin/conversations/9c2b7e14-6a03-4f88-b1d5-70e2c9a4d611/operator-messageSample 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
Filter by free text, owner, lifecycle stage, or restrict to qualified leads.
Query parameters
| Name | Type | Description |
|---|---|---|
| q | string | Free-text search over name, phone and email. |
| owner | uuid | Operator the contact is assigned to. |
| stage | string | Lifecycle stage to filter by. |
| leads_only | boolean | Default false. |
| limit | integer | Default 100. |
Request
curl -G https://api.trykairos.in/v1/admin/contacts \
-H "Authorization: Bearer $KAIROS_API_KEY" \
-d q=sharma \
-d limit=25Response
[
{
"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
https://api.trykairos.in/v1/admin/contacts?q=sharma&limit=25Sample 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
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
| Name | Type | Description |
|---|---|---|
| value | string required | The raw identifier to resolve. |
Request
curl -G https://api.trykairos.in/v1/admin/contacts/by-identity \
-H "Authorization: Bearer $KAIROS_API_KEY" \
--data-urlencode "value=+919876543210"Response
{
"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
https://api.trykairos.in/v1/admin/contacts/by-identity?value=%2B919876543210Sample 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
The contact, every identity that resolves to it, and its lead records. identities[].kind tells you which channel each identifier came from.
Path parameters
| Name | Type | Description |
|---|---|---|
| contact_id | uuid required | The contact id. |
Request
curl https://api.trykairos.in/v1/admin/contacts/7d0e2f45-9a11-4b63-8c27-3e5f6a7b8c90 \
-H "Authorization: Bearer $KAIROS_API_KEY"Response
{
"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
https://api.trykairos.in/v1/admin/contacts/7d0e2f45-9a11-4b63-8c27-3e5f6a7b8c90Sample 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
Create a person by hand. At least one of email or phone should be supplied, otherwise nothing can later resolve to this contact.
Body
| Name | Type | Description |
|---|---|---|
| full_name | string | Display name. |
| string | Becomes an email identity. | |
| phone | string | E.164. Becomes a phone identity. |
| attributes | object | Free-form string map. `interest` is the canonical key for what the person is asking about. |
Request
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
{
"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
https://api.trykairos.in/v1/admin/contactsSample 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
Partial update. Omitted fields are left alone.
Path parameters
| Name | Type | Description |
|---|---|---|
| contact_id | uuid required | The contact id. |
Body
| Name | Type | Description |
|---|---|---|
| full_name | string | Display name. |
| string | Replaces the primary email. | |
| phone | string | Replaces the primary phone. |
| attributes | object | Free-form map; merged into the existing one. |
Request
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
{
"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
https://api.trykairos.in/v1/admin/contacts/7d0e2f45-9a11-4b63-8c27-3e5f6a7b8c90Sample 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
Every company in the workspace, optionally filtered by a name search.
Query parameters
| Name | Type | Description |
|---|---|---|
| q | string | Free-text search over the company name. |
Request
curl -G https://api.trykairos.in/v1/admin/crm/companies \
-H "Authorization: Bearer $KAIROS_API_KEY" \
-d q=acmeResponse
{
"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
https://api.trykairos.in/v1/admin/crm/companies?q=acmeSample 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
name is the only required field.
Body
| Name | Type | Description |
|---|---|---|
| name | string required | 1 to 200 characters. |
| domain | string | Primary web domain. |
| industry | string | Free text. |
| gst_number | string | GSTIN, if you hold one. |
| notes | string | Free text. |
Request
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
{
"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
https://api.trykairos.in/v1/admin/crm/companiesSample 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
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
| Name | Type | Description |
|---|---|---|
| stage | string | Restrict to one stage. |
| company_id | uuid | Restrict to one company. |
Request
curl -G https://api.trykairos.in/v1/admin/crm/deals \
-H "Authorization: Bearer $KAIROS_API_KEY" \
-d stage=proposalResponse
{
"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
https://api.trykairos.in/v1/admin/crm/deals?stage=proposalSample 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
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
| Name | Type | Description |
|---|---|---|
| title | string required | 1 to 200 characters. |
| stage | string | Defaults to "new". |
| amount_paise | integer | Defaults to 0. Must be 0 or more. |
| company_id | uuid | Company the deal belongs to. |
| contact_id | uuid | Person the deal belongs to. |
| expected_close_date | date | YYYY-MM-DD. |
| source | string | Free text. |
| notes | string | Free text. |
Request
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
{
"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
https://api.trykairos.in/v1/admin/crm/dealsSample 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
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
| Name | Type | Description |
|---|---|---|
| deal_id | uuid required | The deal id. |
Body
| Name | Type | Description |
|---|---|---|
| stage | string required | Target stage. |
| position | number | Sort position within the stage. Omit to append. |
Request
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
{
"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
https://api.trykairos.in/v1/admin/crm/deals/8b7a6c5d-4e3f-4021-9a8b-7c6d5e4f3a21/stageSample 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
Offset-paginated, with a counts map alongside so you can badge a queue without a second call.
Query parameters
| Name | Type | Description |
|---|---|---|
| status | string | One of open, in_progress, waiting, done, cancelled. |
| ticket_type | string | One of followup_call, send_message, human_review, approval, other. |
| assignee_kind | string | ai or human. |
| assignee_user_id | string | Restrict to one assignee. |
| team_id | uuid | Restrict to one team. |
| conversation_id | uuid | Tickets raised from one conversation. |
| due_before | date-time | Only tickets due at or before this instant, soonest first. |
| counts_due_before | date-time | Adds a `due` key to counts: open and in_progress tickets due by this instant. |
| statuses | string | Comma-separated statuses, for a folder spanning several. |
| limit | integer | Default 50. |
| offset | integer | Default 0. |
Request
curl -G https://api.trykairos.in/v1/admin/tickets \
-H "Authorization: Bearer $KAIROS_API_KEY" \
-d status=open \
-d limit=20Response
{
"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
https://api.trykairos.in/v1/admin/tickets?status=open&limit=20Sample 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
Raise work by hand. source is always manual on this route; disposition and escalation are written by the platform only.
Body
| Name | Type | Description |
|---|---|---|
| title | string required | 1 to 300 characters. |
| ticket_type | string | One of followup_call, send_message, human_review, approval, other. Defaults to "other". |
| detail | string | Free text. |
| conversation_id | uuid | Conversation this relates to. |
| assignee_kind | string | ai or human. |
| assignee_agent_install_id | uuid | Agent to assign to, when assignee_kind is ai. |
| assignee_user_id | string | Person to assign to. |
| team_id | uuid | Team to assign to. |
| due_at | date-time | ISO-8601 instant. |
| contact_identifier | string | Phone or email the work concerns. |
Request
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
{
"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
https://api.trykairos.in/v1/admin/ticketsSample 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
Partial update. A ticket id from another workspace returns 404, the same as an unknown id.
Path parameters
| Name | Type | Description |
|---|---|---|
| ticket_id | uuid required | The ticket id. |
Body
| Name | Type | Description |
|---|---|---|
| status | string | One of open, in_progress, waiting, done, cancelled. |
| title | string | New title. |
| detail | string | New detail. |
| due_at | date-time | New due instant. |
| team_id | uuid | Reassign to a team. |
| draft | string | Draft reply held against the ticket. |
Request
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
{
"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
https://api.trykairos.in/v1/admin/tickets/6f5e4d3c-2b1a-4098-8765-4321fedcba09Sample 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
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 https://api.trykairos.in/v1/admin/documents \
-H "Authorization: Bearer $KAIROS_API_KEY"Response
[
{
"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
https://api.trykairos.in/v1/admin/documentsSample 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
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)
| Name | Type | Description |
|---|---|---|
| file | file required | The document to ingest. |
| agent_installation_id | uuid (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 -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
{
"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
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.
https://api.trykairos.in/v1/admin/documentsSample 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
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
| Name | Type | Description |
|---|---|---|
| query | string required | 1 to 500 characters. |
| top_k | integer | Chunks to retrieve. 1 to 10, default 4. |
Request
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
{
"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
https://api.trykairos.in/v1/admin/documents/test-retrievalSample 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
Removes the document and every chunk indexed from it. No response body.
Path parameters
| Name | Type | Description |
|---|---|---|
| document_id | uuid required | The document id. |
Request
curl -X DELETE https://api.trykairos.in/v1/admin/documents/5b1f0c77-2d43-4a90-8e11-c6d7e8f90a12 \
-H "Authorization: Bearer $KAIROS_ADMIN_TOKEN"Response
204 No ContentTry itSample response
https://api.trykairos.in/v1/admin/documents/5b1f0c77-2d43-4a90-8e11-c6d7e8f90a12Sample 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
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 https://api.trykairos.in/v1/admin/webhooks \
-H "Authorization: Bearer $KAIROS_API_KEY"Response
[
{
"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
https://api.trykairos.in/v1/admin/webhooksSample 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
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 https://api.trykairos.in/v1/admin/webhooks/events \
-H "Authorization: Bearer $KAIROS_API_KEY"Response
[
"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
https://api.trykairos.in/v1/admin/webhooks/eventsSample 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
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
| Name | Type | Description |
|---|---|---|
| label | string required | 1 to 100 characters. |
| url | string required | Public https endpoint, up to 2000 characters. |
| events | string[] | Event names from GET /v1/admin/webhooks/events. Unknown names are dropped. |
Request
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
{
"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
https://api.trykairos.in/v1/admin/webhooksSample 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
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
| Name | Type | Description |
|---|---|---|
| sub_id | uuid required | The subscription id. |
Body
| Name | Type | Description |
|---|---|---|
| enabled | boolean required | Whether to deliver. |
Request
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
{
"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
https://api.trykairos.in/v1/admin/webhooks/aa11bb22-cc33-4d44-9e55-ff6600771188Sample 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
What we sent and what came back. status_code is null when the request never completed, and error then carries why.
Path parameters
| Name | Type | Description |
|---|---|---|
| sub_id | uuid required | The subscription id. |
Request
curl https://api.trykairos.in/v1/admin/webhooks/aa11bb22-cc33-4d44-9e55-ff6600771188/attempts \
-H "Authorization: Bearer $KAIROS_API_KEY"Response
[
{
"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
https://api.trykairos.in/v1/admin/webhooks/aa11bb22-cc33-4d44-9e55-ff6600771188/attemptsSample 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
Removes the subscription. No response body.
Path parameters
| Name | Type | Description |
|---|---|---|
| sub_id | uuid required | The subscription id. |
Request
curl -X DELETE https://api.trykairos.in/v1/admin/webhooks/aa11bb22-cc33-4d44-9e55-ff6600771188 \
-H "Authorization: Bearer $KAIROS_ADMIN_TOKEN"Response
204 No ContentTry itSample response
https://api.trykairos.in/v1/admin/webhooks/aa11bb22-cc33-4d44-9e55-ff6600771188Sample 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
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 https://api.trykairos.in/v1/admin/wallet \
-H "Authorization: Bearer $KAIROS_API_KEY"Response
{
"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
https://api.trykairos.in/v1/admin/walletSample 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
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
| Name | Type | Description |
|---|---|---|
| range | integer | Days to cover. Default 30. |
Request
curl -G https://api.trykairos.in/v1/admin/wallet/usage \
-H "Authorization: Bearer $KAIROS_API_KEY" \
-d range=7Response
{
"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
https://api.trykairos.in/v1/admin/wallet/usage?range=7Sample 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
Metadata only: label, prefix, and when the key was last used. The key itself is never returned by a read.
Request
curl https://api.trykairos.in/v1/admin/api-keys \
-H "Authorization: Bearer $KAIROS_API_KEY"Response
[
{
"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
https://api.trykairos.in/v1/admin/api-keysSample 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
cleartext is returned exactly once, in this response. Store it before you close the connection.
Body
| Name | Type | Description |
|---|---|---|
| label | string required | 1 to 100 characters. Name it after what will hold it. |
Request
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
{
"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
https://api.trykairos.in/v1/admin/api-keysSample 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
Takes effect immediately. An already-revoked or unknown id is a 404. No response body.
Path parameters
| Name | Type | Description |
|---|---|---|
| key_id | uuid required | The key id. |
Request
curl -X DELETE https://api.trykairos.in/v1/admin/api-keys/ab12cd34-ef56-4789-a0b1-c2d3e4f5a6b7 \
-H "Authorization: Bearer $KAIROS_ADMIN_TOKEN"Response
204 No ContentTry itSample response
https://api.trykairos.in/v1/admin/api-keys/ab12cd34-ef56-4789-a0b1-c2d3e4f5a6b7Sample 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.