Skip to content
api reference · v1

Build on Kitsune.

OpenAI-compatible chat completions at https://kitsune.cv/v1. One key, every model, billed per token from your balance. Streaming, tools, vision and reasoning follow the standard schema.

base url https://kitsune.cv/v1
No SDK required SSE streaming Pay as you go No prompt storage
01

Quickstart

from zero to a streamed answer

1

Create a key

Sign in, open Console → API keys, and create a key. It starts with kts-, is shown once, and can carry a spend cap and an expiry date. Revoking it takes effect immediately.

2

Send a request

No SDK required. Send JSON over HTTPS with the key as a Bearer token.

curl https://kitsune.cv/v1/chat/completions \
  -H "Authorization: Bearer $KITSUNE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimax-m3.1-flash",
    "messages": [
      {"role": "user", "content": "Explain HTTP caching in two sentences."}
    ]
  }'

base https://kitsune.cv/v1 · Authorization: Bearer kts-…

Raw HTTP in every language — no SDK package is required.

3

Stream tokens

Set stream: true to receive server-sent events. Add stream_options.include_usage to get a final usage object.

curl -N https://kitsune.cv/v1/chat/completions \
  -H "Authorization: Bearer $KITSUNE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimax-m3.1-flash",
    "messages": [{"role": "user", "content": "Count to five."}],
    "stream": true,
    "stream_options": {"include_usage": true}
  }'

The stream ends with data: [DONE].

02

Install

wire up a coding agent in one command

The installer asks for your key, reads the catalogue, and writes each agent's config with every model your key can use. It only touches files in your home directory, and it backs up anything it rewrites.

curl -fsSL https://kitsune.cv/install | bash

Run it from a terminal — the key prompt needs one.

1

Paste a key

It is checked against /v1/models before anything is written, so a revoked or out-of-credit key fails immediately. Create one at Console → API keys.

2

Pick the agents

Agents already on the machine are pre-selected. OpenCode, [CC], Codex CLI, Aider and Zed are configured for you; env writes ~/.config/kitsune/env for anything else that reads OPENAI_BASE_URL and OPENAI_API_KEY.

3

Pick a default model

Every model in the catalogue is added; the one you choose becomes the default. Switch at any time from the agent's own model picker.

Flags: --agent, --all, --list, --yes, --dry-run, --force. An agent already pointed at a different endpoint is skipped until you pass --force.

03

Authentication

bearer tokens, scoped in the console

Every request needs an Authorization: Bearer header. Keys are created in the console, can carry a spend cap and expiry, and are revoked instantly. Legacy sk-dr-… keys created before the rename still authenticate.

Header
Authorization: Bearer kts-…
Key format
kts- + 48 hexadecimal characters
Billing
One rail — pay as you go, charged from the account balance.
CORS
No CORS headers are sent. Call the API from a server, not from another origin in a browser.
Keys are hashed at rest. Kitsune never shows a secret a second time — copy it when it is created.
04

Models & pricing

usd per 1m tokens · live catalog

Prices and capabilities come from the live catalog. A model with no cache price bills cached prompt tokens at the input price — the cache column reads = input.

modelinputcacheoutputcontextmax outcapabilities
MiniMax M3.1 Flash
minimax-m3.1-flash
$0.05$0.004$0.191.0M500k
vision thinking tools
DeepSeek V4.1 Flash 1017
deepseek-v4.1-flash-1017
$0.09$0.004$0.191.0M128k
vision thinking tools
DeepSeek V4.1 Flash 1017 Abliterated
deepseek-v4.1-flash-1017-abliterated
$0.20$0.009$0.451.0M128k
vision thinking tools

Get the same list programmatically from GET /v1/catalog (public) or GET /v1/models (key-scoped).

Some models can be restricted to selected accounts. /v1/models only returns models the key can use; a granted account may also see a free_quota object with the remaining monthly input/output token allowance.
05

Chat completions

POST /v1/chat/completions

Send model and messages. A message has a role and content: system, developer, user, assistant or tool. Content is a string or an array of content parts for images.

Response

The assistant reply is in choices[0].message.content. Usage is reported by the model and billed for that turn.

json
{
  "id": "chatcmpl-7f2c1b1e",
  "object": "chat.completion",
  "created": 1762300000,
  "model": "minimax-m3.1-flash",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "HTTP caching stores responses so later requests can reuse them."
    },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 21,
    "completion_tokens": 38,
    "total_tokens": 59,
    "prompt_tokens_details": { "cached_tokens": 0 }
  }
}
Zero data retention. Prompt and completion bodies are not stored. Request metadata — model, token counts, cost, latency and status — is kept for billing and the usage page.
06

Streaming

server-sent events

Set stream: true. Content arrives in choices[0].delta.content, thinking in delta.reasoning_content, and tool calls as indexed fragments in delta.tool_calls. The stream ends with data: [DONE].

raw stream
data: {"choices":[{"index":0,"delta":{"role":"assistant","content":"HTTP "},"finish_reason":null}]}

data: {"choices":[{"index":0,"delta":{"content":"caching "},"finish_reason":null}]}

data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"choices":[],"usage":{"prompt_tokens":21,"completion_tokens":38,"total_tokens":59}}

data: [DONE]
Streaming usage is only sent when you ask for it with stream_options.include_usage. Without it, billing still uses the token counts reported for the turn.
07

Thinking level

reasoning_effort · model dependent

Send reasoning_effort: "low" | "medium" | "high" | "max". The gateway forwards it unchanged; max asks for the deepest budget the model offers, and the model decides how much to think. Reasoning text comes back as message.reasoning_content — some models use reasoning or thinking, and those fields pass through untouched.

curl https://kitsune.cv/v1/chat/completions \
  -H "Authorization: Bearer $KITSUNE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimax-m3.1-flash",
    "messages": [
      {"role": "user", "content": "Design a retry strategy for a payment API."}
    ],
    "reasoning_effort": "high"
  }'

Thinking tokens are billed as completion tokens reported for the turn.

Whether a model actually reasons is shown by the thinking badge in the model table above and on Live models.

08

Tool calling

proxy, not an executor

Kitsune forwards tools, tool_choice and parallel_tool_calls, then returns the model's tool_calls. Your code executes the function and sends the result back as a role:"tool" message with the same tool_call_id before calling again.

curl https://kitsune.cv/v1/chat/completions \
  -H "Authorization: Bearer $KITSUNE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimax-m3.1-flash",
    "messages": [{"role": "user", "content": "What is the time in Tashkent?"}],
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_time",
        "description": "Return the current time for a city.",
        "parameters": {
          "type": "object",
          "properties": {"city": {"type": "string"}},
          "required": ["city"],
          "additionalProperties": false
        }
      }
    }],
    "tool_choice": "auto",
    "parallel_tool_calls": true
  }'
tool result
// 1. assistant returns a call
{ "role": "assistant", "content": null, "tool_calls": [{
    "id": "call_abc123",
    "type": "function",
    "function": { "name": "get_time", "arguments": "{\"city\":\"Tashkent\"}" }
}] }

// 2. you run it, then append the result
{ "role": "tool", "tool_call_id": "call_abc123",
  "content": "{\"time\":\"2026-11-03T20:30:00Z\"}" }

// 3. call again with the same tools -> final assistant content

The complete multi-turn loop in curl, JavaScript and Python is in the cookbook.

09

Vision & structured output

content parts · response_format

Use a model with the vision badge. Pass content as an array of text and image_url parts — HTTPS URLs and base64 data URLs are both accepted, model permitting.

curl https://kitsune.cv/v1/chat/completions \
  -H "Authorization: Bearer $KITSUNE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimax-m3.1-flash",
    "messages": [{
      "role": "user",
      "content": [
        {"type": "text", "text": "What is in this image?"},
        {"type": "image_url", "image_url": {"url": "https://example.com/chart.png"}}
      ]
    }]
  }'

Switch tabs for JSON-schema structured output.

response_format also accepts json_object for loose JSON. For strict schemas, use json_schema with strict: true where the model supports it.

10

System prompt & time

applied by the gateway

Kitsune prepends the admin main system prompt and the model's own prompt, keeps your role:"system" message, then appends the current UTC time when the model has time context enabled. The time goes at the end so the stable prefix stays cacheable.

what the model receives
system: <main prompt>

        <model prompt>

        <your system message>

        Current UTC time: 2026-11-03 20:30:00 UTC
user:   Explain HTTP caching...

The injected text counts as prompt tokens and is billed normally.

11

Billing & cache

pay as you go · sub-cent accrual

Each successful request is priced from the model row at the time of the call:

pricing
cost = uncached_input × input_price
     + cached_input   × cache_price
     + output_tokens  × output_price
  • When a model has no cache price, cached prompt tokens bill at the input price.
  • Cache hits are reported as prompt_tokens_details.cached_tokens or cache_read_input_tokens; both are counted and never exceed the prompt.
  • Sub-cent amounts accrue on the account and are charged in whole cents as they cross the threshold.
  • Failed calls are not billed. Aborted streams are billed for the usage reported for the turn.
  • An empty balance returns 402 insufficient_balance before the request is sent.

Response headers

headers
x-kitsune-billing: payg
content-type: application/json

x-kitsune-billing reports the billing mode used for the request.

12

Failover

ordered route chain

A model can carry more than one route. Where a second route is wired, the first healthy one takes the request and a retryable failure moves to the next — transport errors, 401/402/403/408/409/425/429 and 5xx. A failing route is rested with an exponential cooldown.

Some models are served from a single route today. If that route fails the request returns an error instead of switching models.
13

Errors

json body · stable codes

Errors use a single envelope. Branch on error.code, not the message.

json
{
  "error": {
    "message": "balance is empty — top up to keep using the API",
    "type": "gateway_error",
    "code": "insufficient_balance"
  }
}
statuscodemeaning
400bad_requestthe body or model id is invalid
401unauthorizedmissing or revoked key
402insufficient_balancetop up to continue
403key_scopethe key cannot use pay-as-you-go
403model_not_allowedrestricted model and the account has no grant
404model_not_foundsee /v1/models
404not_foundunknown key id
413payload_too_largerequest body is above 25 MB
429rate_limitedmore than 600 requests/min for the account; wait and retry
500internalunexpected gateway error
503no_routeevery route in the chain failed
14

Endpoints

everything the gateway exposes

methodpathauthnotes
POST/v1/chat/completionsBearerchat, streaming, tools, vision, reasoning
GET/v1/modelsBearermodels available to the key, cache prices included
GET/v1/models/{id}Bearerone model
GET/v1/catalogpublicprice list, display names, capabilities
GET/v1/accountBearerbalance, accrued, key count, models
GET/v1/planBeareralias of /v1/account
GET/v1/usage?days=30Bearerdaily, per-model and per-key usage
GET/v1/keysBearerlist keys
POST/v1/keysBearercreate a key; the secret is returned once
PATCH/v1/keys/{id}Bearerlabel, spend cap
DELETE/v1/keys/{id}Bearerrevoke; send Content-Type: application/json

Account, usage & keys

The same key manages the account it belongs to. Key creation returns the secret once — store it before leaving the response.

curl https://kitsune.cv/v1/account \
  -H "Authorization: Bearer $KITSUNE_KEY"

curl "https://kitsune.cv/v1/usage?days=30" \
  -H "Authorization: Bearer $KITSUNE_KEY"

curl https://kitsune.cv/v1/keys \
  -H "Authorization: Bearer $KITSUNE_KEY"

# create a key (secret is returned once)
curl https://kitsune.cv/v1/keys \
  -H "Authorization: Bearer $KITSUNE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label":"ci","spend_cap_cents":500,"expires_days":90}'
15

Request parameters

OpenAI schema + pass-through

fieldtypenotes
modelstringpublic id from the catalog
messagesarraysystem, developer, user, assistant, tool; content string or parts
streamboolSSE; ends with data: [DONE]
stream_optionsobject{"include_usage":true} adds a final usage chunk
temperature · top_pnumbersampling; forwarded to the model
max_tokens · max_completion_tokensintoutput cap; either alias is forwarded
stop · n · seedmixedstop sequences, choice count, best-effort determinism
presence_penalty · frequency_penalty · logit_biasmixedsampling controls; forwarded
logprobs · top_logprobsbool / inttoken log probabilities
tools · tool_choicearray / stringJSON-Schema function calling; auto, none, required, or a named function
parallel_tool_callsboolallow several tool calls in one turn
response_formatobjecttext, json_object, json_schema
reasoning_effortstringlow · medium · high · max; model-dependent
user · metadata · store · service_tiermixedforwarded unchanged; support depends on the model

Any other JSON field is forwarded unchanged. Kitsune does not execute tools, browse, or store prompts. The complete parameter table and every request example live in the cookbook.

16

Full cookbook

copy, paste, call

Every feature, every example.

Basic chat, streaming, thinking, the complete tool-result loop, parallel tools, vision, structured output, sampling, response schema, headers and HTTP setup — with the full JSON responses.