Chat sessions with your own ID
Your system already has a name for each conversation: a support ticket, an order, a chat thread in your app. With external_id, that name is the K-Agent session — no mapping table to keep. This guide builds a complete integration on one rule from the session contract: POST /v1/sessions is get-or-create and always answers its input.
One call per turn
Section titled “One call per turn”For every message your customer sends, make one call:
const BASE = 'https://api.k-agent.kerneltics.com/v1';
/** * Sends one customer message to the agent and returns what to show them. * threadId: your conversation ID, e.g. "thread-58123" * messageId: your ID for this message — reused if you retry, so it is answered once */export async function askAgent({ threadId, messageId, customer, text }) { const res = await fetch(`${BASE}/sessions`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.KAGENT_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': `msg-${messageId}`, }, body: JSON.stringify({ agent: 'store-assistant', external_id: threadId, end_user: { external_id: customer.id, name: customer.firstName }, input: text, }), }); const body = await res.json(); if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
if (!body.run) return { kind: 'with_team' }; // human mode: your team has it if (['queued', 'in_progress'].includes(body.run.status)) return { kind: 'pending', runId: body.run.id }; if (body.run.outcome === 'handed_off') return { kind: 'handed_off', text: body.run.output_text }; return { kind: 'reply', text: body.run.output_text };}import os, requests
BASE = "https://api.k-agent.kerneltics.com/v1"
def ask_agent(thread_id: str, message_id: str, customer: dict, text: str) -> dict: """Sends one customer message to the agent and returns what to show them.""" r = requests.post( f"{BASE}/sessions", headers={ "Authorization": f"Bearer {os.environ['KAGENT_API_KEY']}", "Idempotency-Key": f"msg-{message_id}", # a retry is answered once }, json={ "agent": "store-assistant", "external_id": thread_id, "end_user": {"external_id": customer["id"], "name": customer["first_name"]}, "input": text, }, timeout=120, ) body = r.json() if not r.ok: raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
if body["run"] is None: return {"kind": "with_team"} # human mode: your team has it if body["run"]["status"] in ("queued", "in_progress"): return {"kind": "pending", "run_id": body["run"]["id"]} if body["run"]["outcome"] == "handed_off": return {"kind": "handed_off", "text": body["run"]["output_text"]} return {"kind": "reply", "text": body["run"]["output_text"]}curl https://api.k-agent.kerneltics.com/v1/sessions \ -H "Authorization: Bearer $KAGENT_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: msg-77120" \ -d '{ "agent": "store-assistant", "external_id": "thread-58123", "end_user": { "external_id": "cus_1042", "name": "Fahad" }, "input": "Where is my order?" }'What this gives you:
- The first message creates the session (
201); every later one resumes it (200). You never need to look up asess_ID. - Retries are safe. An
Idempotency-Keyderived from your own message ID means a network retry returns the stored result instead of a second answer. See Idempotency. - History is kept for you. The agent sees the conversation so far (
history_limitmessages), even days later. - Handoffs are visible.
outcome: "handed_off"means your team now owns the conversation. From then onsession.modeis"human",runisnulland no AI reply is produced until the handoff is released or expires.
Choose good IDs
Section titled “Choose good IDs”- IDs are unique per project, and one ID belongs to one agent. If several agents work on the same records, prefix the ID:
support:thread-58123,sales:thread-58123. - Allowed characters are letters, digits and
. _ : -, up to 128, starting with a letter or digit. Don’t start withsess_. - Never use a phone number or email as the ID. If that is your natural key, hash it on your server.
- Pass the person as
end_user. A session’s end user can’t change later, and the agent’s memory of past actions follows the end user, not the session.
Replies that take longer, or come from your team
Section titled “Replies that take longer, or come from your team”Some conversations are best handled without holding a request open: email, SMS, or any channel where you push replies to the customer. Send with background: true and receive replies by webhook:
curl https://api.k-agent.kerneltics.com/v1/sessions/thread-58123/messages \ -H "Authorization: Bearer $KAGENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"input": "Perfect, thanks!", "client_message_id": "77121", "background": true}'The call returns 202 at once. Subscribe an endpoint to message.created: it fires for the agent’s replies and for your team’s public replies from the Handoff Desk, with role and author, so one handler delivers both:
{ "type": "message.created", "id": "evt_01k6rz8e0h2k4n6q8s0v2x4z6b", "created_at": 1791272100, "data": { "message": { "id": "msg_01k6rz8e0h2k4n6q8s0v2x4z6b", "object": "message", "session_id": "sess_01k6rz4p7h2c9m5x8w3t6v1qbg", "role": "assistant", "content": [{ "type": "text", "text": "You're welcome — it should reach you within 1 to 3 working days." }], "created_at": 1791272100 } }}The payload carries the session’s sess_ ID. Keep it from your first call (responses always return both IDs), or fetch the session to read its external_id.
Read, close and clean up
Section titled “Read, close and clean up”| Task | Call |
|---|---|
| Show the transcript | GET /v1/sessions/thread-58123/messages |
| Follow the conversation live | GET /v1/sessions/thread-58123/events |
| Close a resolved conversation | POST /v1/sessions/thread-58123/close — a new message reopens it |
| Hand over from your side | POST /v1/sessions/thread-58123/handoff with {"reason_type": "customer_requested", "summary": "…"} |
| Delete it completely | DELETE /v1/sessions/thread-58123 |
Common errors
Section titled “Common errors”| Code | Fix |
|---|---|
session_agent_mismatch |
The ID already belongs to another agent; prefix IDs per agent. |
session_end_user_mismatch |
The thread was started for another customer; don’t reuse thread IDs across customers. |
session_busy / session_queue_full |
Messages arrived faster than the agent answers; retry after Retry-After, or use concurrency: "queue". |
external_id_invalid |
The ID has characters outside A–Z a–z 0–9 . _ : -, or starts with sess_. |