API Reference
The Khwan REST API. The Python and TypeScript clients are thin wrappers over these endpoints — you can call them directly from any language.
- Base URL:
https://api.khwan.ai - Content type:
application/jsonfor all request and response bodies. - Errors: any non-2xx response is an error. The body carries a message; common codes are listed under Errors.
Authentication
Every request carries these headers:
| Header | Required | Purpose |
|---|---|---|
X-API-Key | Yes | Your Khwan API key (kwk_live_...). Authenticates the account. |
X-Khwan-User | Recommended | The end-user id. Identifies the user on the request; does not select an isolated brain. |
X-Khwan-Core | Optional | Core slug. Selects an isolated brain within the account. Omit for the account’s default core. |
POST /prepare HTTP/1.1
Host: api.khwan.ai
X-API-Key: kwk_live_xxx
X-Khwan-User: alice
X-Khwan-Core: client1
Content-Type: application/jsonX-API-Key is your Khwan key, not your model provider’s key. In the BYOM flow
Khwan never receives your Anthropic / OpenAI key.
Isolation is by core (and by account), not by X-Khwan-User. See Cores.
POST /prepare
Builds the context for one turn (memory + constitution + coherence). Does not call an LLM.
Request
{
"input": "remember I prefer short answers in Thai",
"model": "claude-sonnet-4-6",
"constitution": "support-agent-v2"
}| Field | Type | Required | Notes |
|---|---|---|---|
input | string | Yes | The end user’s message. |
model | string | No | Session model hint; may be overridden by dashboard settings. |
constitution | string | No | Named constitution profile to apply. |
Response
{
"messages": [
{ "role": "system", "content": "<constitution + memory + lessons + coherence>" },
{ "role": "user", "content": "remember I prefer short answers in Thai" }
],
"coherence": 0.92,
"sources": [{ "id": "mem_123", "text": "prefers Thai", "score": 0.81 }],
"allowed": true,
"reason": null,
"turn_token": "tt_9f2c..."
}Feed messages to your own model, then send turn_token back to /record. If
allowed is false, skip generation and surface reason.
POST /record
Persists your model’s answer and lets Khwan learn from the turn.
Request
{
"turn_token": "tt_9f2c...",
"answer": "Got it. I'll keep my answers short."
}| Field | Type | Required | Notes |
|---|---|---|---|
turn_token | string | Yes | The token from the matching /prepare. |
answer | string | Yes | The text your model produced. |
Response — 200 OK, a small acknowledgement object (may be empty).
{ "ok": true }POST /sync
Triggers a learning / consolidation pass for the selected core (distilling lessons from recent turns).
Request — no body required.
Response
{ "ok": true, "learned": 3 }GET /memory
Returns recent memory entries for the selected core.
Query parameters
| Param | Type | Default | Notes |
|---|---|---|---|
limit | integer | 20 | Max entries to return. |
GET /memory?limit=20
X-API-Key: kwk_live_xxx
X-Khwan-Core: client1Response
{
"items": [
{ "id": "mem_123", "text": "prefers short answers in Thai", "created_at": "2026-07-16T10:00:00Z" }
]
}GET /metrics
Returns coherence / learning / usage metrics for the selected core.
GET /metrics
X-API-Key: kwk_live_xxx
X-Khwan-Core: client1Response
{
"turns": 128,
"avg_coherence": 0.9,
"memory_size": 42,
"lessons": 7
}Cores
A core is a fully isolated brain within an account — its own memory,
identity, and learning. Select one on any request with the X-Khwan-Core header;
omit it to target the account’s default core. Quota is pooled at the account level.
Slugs match ^[a-z0-9][a-z0-9-]{0,38}$. The default core is virtual — always
available, and cannot be created or deleted.
POST /cores
Create a core. Admin key required.
Request
{ "slug": "client1", "name": "Client One", "context": "Acme support desk" }| Field | Type | Required | Notes |
|---|---|---|---|
slug | string | Yes | Matches ^[a-z0-9][a-z0-9-]{0,38}$. Immutable once created. |
name | string | Yes | Human-readable label. |
context | string | No | Seed context / description for the core. |
Response — the created core.
{ "slug": "client1", "name": "Client One", "context": "Acme support desk" }GET /cores
List the account’s cores (includes the virtual default).
{
"items": [
{ "slug": "default", "name": "Default" },
{ "slug": "client1", "name": "Client One" }
]
}GET /cores/{slug}
Fetch a single core.
{ "slug": "client1", "name": "Client One", "context": "Acme support desk" }PATCH /cores/{slug}
Update a core’s name or context (the slug is immutable). Admin key required.
Request
{ "name": "Client One — EU", "context": "Acme support desk, EU region" }DELETE /cores/{slug}
Delete a core and its memory. Admin key required. The default core cannot be
deleted.
Response — 200 OK.
{ "ok": true }Errors
Any non-2xx status is an error; the body contains a message. Frequently seen:
| Status | Meaning |
|---|---|
401 | Unauthorized — bad or missing API key. |
402 | Payment required — add a payment method / upgrade your plan. |
429 | Quota exceeded — over your plan’s limit. |
The clients raise a typed error on these (for example KhwanError in Python, with
a .status code).