# Quickstart (5 minutes)

> Create an agent and a secret key, ask a one-shot question, start a session with your own ID and stream a reply — with curl, JavaScript and Python.

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

1. Sign up at [app.k-agent.kerneltics.com](https://app.k-agent.kerneltics.com). You get an organization on the Free plan and a project called **Production**.
2. 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

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:

```bash
export KAGENT_API_KEY="kt_sk_live_…"
```

## 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**

```bash
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."
      }
    }
  }'
```

**JavaScript**

```js
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);
```

**Python**

```python
import os
import 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:

```json
{
  "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
}
```

:::tip[Not ready?]
If `readiness.blockers` contains `no_model_credential`, connect a provider key (step 1). Calls to an agent that isn't ready return `409 agent_not_ready` and never a half-answer.
:::

## 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**

```bash
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?"}'
```

**JavaScript**

```js
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);
```

**Python**

```python
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`:

```json
{
  "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

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**

```bash
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?"}'
```

**JavaScript**

```js
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);
```

**Python**

```python
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"])
```

```json
{
  "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](/docs/en/concepts/sessions/).

## 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**

```bash
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}'
```

**JavaScript**

```js
// 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;
}
```

**Python**

```python
import codecs
import 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"):
            break
```

The stream looks like this. `message.delta` events are best-effort previews; `message.completed` and `run.completed` are authoritative:

```text
id: 4182
event: run.created
data: {"run":{"id":"run_01k6rz7d9f1h3k5n7q9s1v3x5z","object":"run","status":"queued"}}

event: message.delta
data: {"message_id":"msg_01k6rz8e0h2k4n6q8s0v2x4z6b","delta":"Yes, within 7 days,"}

event: message.delta
data: {"message_id":"msg_01k6rz8e0h2k4n6q8s0v2x4z6b","delta":" as long as it's unopened and in its original packaging."}

id: 4185
event: message.completed
data: {"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: 4186
event: run.completed
data: {"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](/docs/en/concepts/runs-and-streaming/).

## What's next

- Put the agent on your website with the [web widget](/docs/en/guides/widget/).
- Let it call your systems with [HTTP tools](/docs/en/guides/http-tools/).
- Hear about handoffs and replies with [webhooks](/docs/en/guides/webhooks/).
- Keep your OpenAI code: [use the OpenAI SDK](/docs/en/guides/openai-sdk/).
- Tune every setting in the [settings reference](/docs/en/concepts/settings/).
