Management API
Everything the console does, from code. Model calls stay on the Model API. This API runs the account.
Who calls it
The base is https://app.klinn.dev/api/v1. Agents send their wallet session (kla_…). Backends send an agent key (kl_rs_…) made in the console on Home or API keys. Model calls stay at https://api.klinn.dev/v1. The contract is one OpenAPI 3.1 document at /api/v1/openapi.json.
The shape
Inside v1 we only ever add: new endpoints, new optional fields. Ignore fields you do not know. Lists return { data, nextCursor }. Pass nextCursor back as cursor. Errors carry a stable code, a requestId and, when there is one, next: the call that fixes it.
Send an Idempotency-Key on creates so a retry never applies twice. Recording a customer payment requires one.
export KLINN=https://app.klinn.dev/api/v1
export KEY=kl_rs_… # or an agent session, kla_…
curl -s $KLINN/whoami -H "Authorization: Bearer $KEY"
curl -s $KLINN/customers -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: user_8f2k1" -d '{ "name": "Northwind Labs", "externalRef": "user_8f2k1" }'
curl -s $KLINN/domains -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "hostname": "api.northwind.ai" }' # returns the DNS record to createGateways
A gateway is a named config a request runs through. A chat gateway routes the calls your code sends: fallbacks between models, retries, a response cache, a rate limit, a time limit and the models allowed. A decision gateway is your own classifier: it keeps a decision model and the questions, so a call sends only the text. The config is the object the console saves (snake-case fields, validated the same way); unknown fields are dropped and a bad one answers 400 naming the field.
GET /gateways list: name, kind, default model, counts
POST /gateways create from a config (201; 409 when the name is taken)
GET /gateways/{name} the stored config and the keys it is the default of
PUT /gateways/{name} replace the config (a different name renames it)
DELETE /gateways/{name} 204; its keys go back to no gateway
GET /gateways/{name}/stats?range=24h|7d the console's counters: requests, p95, errors, each rule
GET /gateways/{name}/decisions?range=24h|7d a decision gateway's answers per question
PATCH /keys/{id} { "gateway": "<name>" | null } a key's default gatewayA chat gateway with a fallback and a one-hour cache:
curl -s $KLINN/gateways -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: gw-support-bot" -d '{
"name": "support-bot",
"default_model": "claude-sonnet-5",
"strategy": { "mode": "fallback" },
"targets": [{ "model": "claude-sonnet-5" }, { "model": "kimi-k3" }],
"cache": { "mode": "simple", "max_age_s": 3600 }
}'A decision gateway with three questions. Its default_model must be a decision model (kind: "decisions" in GET /api/v1/models); a question is a choice with 2 to 64 options, a score (optionally with levels, lowest first) or noul, yes or no.
curl -s $KLINN/gateways -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{
"name": "support-triage",
"kind": "decision",
"default_model": "pplx-decider-v1.1-27b",
"instructions": "Support messages; the last message is the one to classify.",
"questions": {
"frustrated": { "type": "noul", "instructions": "Is the customer frustrated?" },
"topic": { "type": "choice", "options": ["refund", "bug", "question"] },
"urgency": { "type": "score", "options": ["can wait", "this week", "today"] }
}
}'Attach a gateway to a key as its default, or name it per request with the x-klinn-gateway header; the header wins. Deleting a gateway sends its keys back to no gateway. Every change reaches requests within about a minute, a replaced config, a rename, a delete or a key's new default alike: the router keeps a key's gateway and the gateway list for about 60 seconds, so until then a request may still run through the old config.
curl -s -X PATCH $KLINN/keys/$KEY_ID -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "gateway": "support-bot" }' # { "gateway": null } clears it
curl https://api.klinn.dev/v1/chat/completions -H "Authorization: Bearer $KLINN_API_KEY" \
-H "x-klinn-gateway: support-bot" -H "Content-Type: application/json" \
-d '{ "model": "default", "messages": [{ "role": "user", "content": "Hello" }] }'
curl -s "$KLINN/gateways/support-bot/stats?range=7d" -H "Authorization: Bearer $KEY"
curl -s "$KLINN/gateways/support-triage/decisions?range=24h" -H "Authorization: Bearer $KEY"Delegates
Domains work the same for agents: add one, create the DNS record it returns at your DNS host, then call POST /api/v1/domains/{id}/check until it is live. Agent keys and wallets an owner added are delegates. They can do everything except add wallets or create more agent keys.
