Quickstart (5 minutes)
In five minutes you will create an agent, call it once, open a chat session under your own ID and stream a reply. Every step shows curl, JavaScript (fetch, Node 18+) and Python (requests); pick a tab once and the whole site follows.
The agent in this guide answers for Nada Perfumes, an online perfume and oud store: Royal oud oil (12 ml) 450 SAR, white musk perfume (100 ml) 220 SAR, free delivery on orders over 200 SAR (otherwise 25 SAR) in 1 to 3 working days to Riyadh, Jeddah and Dammam, and returns within 7 days if the product is unopened.
1. Create your account and connect a model
Section titled “1. Create your account and connect a model”- Sign up at app.k-agent.kerneltics.com. You get an organization on the Free plan and a project called Production.
- In onboarding, Connect your AI model: paste an API key from OpenAI, Anthropic, DeepSeek or any OpenAI-compatible provider. The key is checked live, encrypted and never shown again. If your K-Agent server already provides a platform model, you can skip this.
2. Create a secret key
Section titled “2. Create a secret key”Open API keys → Create secret key and copy the key. It starts with kt_sk_live_ and is shown only once. Keep it on your server, then export it in your shell:
export KAGENT_API_KEY="kt_sk_live_…"3. Create an agent
Section titled “3. Create an agent”The quickest way is the dashboard: Agents → New agent, pick a template, give it a name. To do the same over the API:
curl https://api.k-agent.kerneltics.com/v1/agents \ -H "Authorization: Bearer $KAGENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Nada assistant", "slug": "store-assistant", "config": { "locale": { "language": "en", "timezone": "Asia/Riyadh" }, "instructions": { "enabled": true, "text": "You answer customers of Nada Perfumes, an online perfume and oud store. Prices: Royal oud oil 12 ml 450 SAR, white musk perfume 100 ml 220 SAR, bakhoor box 95 SAR. Delivery is free on orders over 200 SAR, otherwise 25 SAR, and takes 1 to 3 working days to Riyadh, Jeddah and Dammam, 3 to 5 days to other cities. Returns within 7 days if the product is unopened and in its original packaging." } } }'const BASE = 'https://api.k-agent.kerneltics.com/v1';const headers = { Authorization: `Bearer ${process.env.KAGENT_API_KEY}`, 'Content-Type': 'application/json',};
const res = await fetch(`${BASE}/agents`, { method: 'POST', headers, body: JSON.stringify({ name: 'Nada assistant', slug: 'store-assistant', config: { locale: { language: 'en', timezone: 'Asia/Riyadh' }, instructions: { enabled: true, text: 'You answer customers of Nada Perfumes, an online perfume and oud store. Prices: Royal oud oil 12 ml 450 SAR, white musk perfume 100 ml 220 SAR, bakhoor box 95 SAR. Delivery is free on orders over 200 SAR, otherwise 25 SAR, and takes 1 to 3 working days to Riyadh, Jeddah and Dammam, 3 to 5 days to other cities. Returns within 7 days if the product is unopened and in its original packaging.', }, }, }),});const agent = await res.json();console.log(agent.id, agent.published_version, agent.readiness);import osimport requests
BASE = "https://api.k-agent.kerneltics.com/v1"HEADERS = {"Authorization": f"Bearer {os.environ['KAGENT_API_KEY']}"}
r = requests.post(f"{BASE}/agents", headers=HEADERS, json={ "name": "Nada assistant", "slug": "store-assistant", "config": { "locale": {"language": "en", "timezone": "Asia/Riyadh"}, "instructions": { "enabled": True, "text": "You answer customers of Nada Perfumes, an online perfume and oud store. " "Prices: Royal oud oil 12 ml 450 SAR, white musk perfume 100 ml 220 SAR, bakhoor box 95 SAR. " "Delivery is free on orders over 200 SAR, otherwise 25 SAR, and takes 1 to 3 working days " "to Riyadh, Jeddah and Dammam, 3 to 5 days to other cities. " "Returns within 7 days if the product is unopened and in its original packaging.", }, },})r.raise_for_status()agent = r.json()print(agent["id"], agent["published_version"], agent["readiness"])Settings you leave out keep their defaults, and the response always shows the complete configuration. Creating an agent publishes version 1 straight away, so it can answer immediately:
{ "id": "agt_01k6rz1m3w8q4t7v9x2b5c0dnf", "object": "agent", "name": "Nada assistant", "slug": "store-assistant", "published_version": 1, "has_unpublished_changes": false, "readiness": { "ready": true, "blockers": [] }, "created_at": 1791271800}4. Ask a one-shot question
Section titled “4. Ask a one-shot question”A one-shot ask needs no session: send a question, get the answer. Address the agent by its slug or its agt_… ID.
curl https://api.k-agent.kerneltics.com/v1/agents/store-assistant/ask \ -H "Authorization: Bearer $KAGENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"input": "How much is delivery?"}'const res = await fetch(`${BASE}/agents/store-assistant/ask`, { method: 'POST', headers, body: JSON.stringify({ input: 'How much is delivery?' }),});const run = await res.json();if (!res.ok) throw new Error(`${run.error.code}: ${run.error.message}`);console.log(run.output_text);r = requests.post(f"{BASE}/agents/store-assistant/ask", headers=HEADERS, json={"input": "How much is delivery?"}, timeout=120)r.raise_for_status()print(r.json()["output_text"])The response is a run. output_text holds the answer; outcome tells you whether the agent answered or handed_off:
{ "id": "run_01k6rz5a9d3f6g2h8j4k7m1n5p", "object": "run", "agent": { "id": "agt_01k6rz1m3w8q4t7v9x2b5c0dnf", "version": 1 }, "session_id": null, "mode": "one_shot", "status": "completed", "outcome": "answered", "output_text": "Delivery is free on orders over SAR 200; below that, it's SAR 25.", "handoff": null, "error": null, "usage": { "input_tokens": 1214, "cached_input_tokens": 0, "output_tokens": 17, "model_calls": 1, "units": 0.25, "weight": 1 }, "config_hash": "sha256:4be1c0d6…", "created_at": 1791271860, "completed_at": 1791271861}5. Start a session with your own ID
Section titled “5. Start a session with your own ID”Sessions keep the conversation history. You can use our sess_… ID, or attach your own external_id — here, the customer’s order number order-8812. POST /v1/sessions is get-or-create: the first call creates the session (201), later calls with the same external_id resume it (200). Send input in the same call to add a message and get the reply.
curl https://api.k-agent.kerneltics.com/v1/sessions \ -H "Authorization: Bearer $KAGENT_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"agent": "store-assistant", "external_id": "order-8812", "input": "Where is my order?"}'const res = await fetch(`${BASE}/sessions`, { method: 'POST', headers: { ...headers, 'Idempotency-Key': crypto.randomUUID() }, body: JSON.stringify({ agent: 'store-assistant', external_id: 'order-8812', input: 'Where is my order?' }),});const { session, created, run } = await res.json();console.log(res.status, session.id, created, run.output_text);import uuid
r = requests.post(f"{BASE}/sessions", headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())}, json={"agent": "store-assistant", "external_id": "order-8812", "input": "Where is my order?"}, timeout=120)r.raise_for_status()body = r.json()print(r.status_code, body["session"]["id"], body["created"], body["run"]["output_text"]){ "session": { "id": "sess_01k6rz4p7h2c9m5x8w3t6v1qbg", "object": "session", "external_id": "order-8812", "status": "active", "mode": "agent", "created_at": 1791271920 }, "created": true, "message": { "id": "msg_01k6rz5b2c4d6e8f0g1h3j5k7m", "object": "message", "role": "user" }, "run": { "id": "run_01k6rz6c8e0g2j4m6p8r0t2v4x", "object": "run", "status": "completed", "outcome": "answered", "output_text": "Orders reach Riyadh, Jeddah and Dammam within 1 to 3 working days, and other cities within 3 to 5. Which city is your order going to?" }, "warnings": []}From now on, order-8812 and sess_01k6rz4p7h2c9m5x8w3t6v1qbg both address this session in every URL. The rules are spelled out in Sessions and session IDs.
6. Stream a reply
Section titled “6. Stream a reply”Add "stream": true to receive the reply as Server-Sent Events while the model writes it. This message addresses the session by your external_id:
curl -N https://api.k-agent.kerneltics.com/v1/sessions/order-8812/messages \ -H "Authorization: Bearer $KAGENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"input": "Can I return a perfume?", "stream": true}'// Parses a text/event-stream response into { id, event, data } objects.async function* readEvents(response) { const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); let buffer = ''; let ev = { id: undefined, event: 'message', data: [] }; for (;;) { const { value, done } = await reader.read(); if (done) return; buffer += value; const lines = buffer.split('\n'); buffer = lines.pop(); for (const raw of lines) { const line = raw.endsWith('\r') ? raw.slice(0, -1) : raw; if (line === '') { if (ev.data.length) yield { id: ev.id, event: ev.event, data: JSON.parse(ev.data.join('\n')) }; ev = { id: undefined, event: 'message', data: [] }; } else if (!line.startsWith(':')) { const i = line.indexOf(':'); const field = i === -1 ? line : line.slice(0, i); const value = i === -1 ? '' : line.slice(i + 1).replace(/^ /, ''); if (field === 'id') ev.id = value; else if (field === 'event') ev.event = value; else if (field === 'data') ev.data.push(value); } } }}
const res = await fetch(`${BASE}/sessions/order-8812/messages`, { method: 'POST', headers: { ...headers, Accept: 'text/event-stream' }, body: JSON.stringify({ input: 'Can I return a perfume?', stream: true }),});if (!res.ok) throw new Error((await res.json()).error.code);
for await (const { event, data } of readEvents(res)) { if (event === 'message.delta') process.stdout.write(data.delta); if (event === 'run.completed' || event === 'run.failed') break;}import codecsimport json
def sse_events(response): """Yields (event, data) pairs from a text/event-stream response.""" decoder = codecs.getincrementaldecoder("utf-8")() buffer, event, data = "", "message", [] for chunk in response.iter_content(chunk_size=None): buffer += decoder.decode(chunk) *lines, buffer = buffer.split("\n") for line in lines: line = line.rstrip("\r") if not line: # a blank line ends an event if data: yield event, json.loads("\n".join(data)) event, data = "message", [] elif not line.startswith(":"): # lines starting with ":" are heartbeats field, _, value = line.partition(":") value = value.removeprefix(" ") if field == "event": event = value elif field == "data": data.append(value)
with requests.post(f"{BASE}/sessions/order-8812/messages", headers=HEADERS, json={"input": "Can I return a perfume?", "stream": True}, stream=True, timeout=120) as r: r.raise_for_status() for event, data in sse_events(r): if event == "message.delta": print(data["delta"], end="", flush=True) elif event in ("run.completed", "run.failed"): breakThe stream looks like this. message.delta events are best-effort previews; message.completed and run.completed are authoritative:
id: 4182event: run.createddata: {"run":{"id":"run_01k6rz7d9f1h3k5n7q9s1v3x5z","object":"run","status":"queued"}}
event: message.deltadata: {"message_id":"msg_01k6rz8e0h2k4n6q8s0v2x4z6b","delta":"Yes, within 7 days,"}
event: message.deltadata: {"message_id":"msg_01k6rz8e0h2k4n6q8s0v2x4z6b","delta":" as long as it's unopened and in its original packaging."}
id: 4185event: message.completeddata: {"message":{"id":"msg_01k6rz8e0h2k4n6q8s0v2x4z6b","role":"assistant","content":[{"type":"text","text":"Yes, within 7 days, as long as it's unopened and in its original packaging."}]}}
id: 4186event: run.completeddata: {"run":{"id":"run_01k6rz7d9f1h3k5n7q9s1v3x5z","object":"run","status":"completed","outcome":"answered"}}Other events, such as tool calls, can appear in between. Ignore event types you don’t use; new ones may be added. If the connection drops, resume with the last id you saw — see Runs and streaming.
What’s next
Section titled “What’s next”- Put the agent on your website with the web widget.
- Let it call your systems with HTTP tools.
- Hear about handoffs and replies with webhooks.
- Keep your OpenAI code: use the OpenAI SDK.
- Tune every setting in the settings reference.