Skip to content

Chat sessions with your own ID

View as Markdown

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.

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 };
}

What this gives you:

  • The first message creates the session (201); every later one resumes it (200). You never need to look up a sess_ ID.
  • Retries are safe. An Idempotency-Key derived 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_limit messages), even days later.
  • Handoffs are visible. outcome: "handed_off" means your team now owns the conversation. From then on session.mode is "human", run is null and no AI reply is produced until the handoff is released or expires.
  • 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 with sess_.
  • 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.

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
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_.