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 create

Gateways

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 gateway

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