Skip to content
cookbook · every feature

Copy. Paste. Call.

Full request and response examples for every supported surface: chat, SSE, thinking, tool calling (including the tool-result loop), parallel tools, vision, structured outputs, sampling, system prompts and the account endpoints.

JavaScript, Python and PHP examples use the small api() helper from HTTP setup; cURL and PHP use the raw endpoints directly.

01 · Basic chatnon-streaming
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-…

Response:

{
  "id": "chatcmpl-7f2c1b1e",
  "object": "chat.completion",
  "created": 1762300000,
  "model": "minimax-m3.1-flash",
  "choices": [{
    "index": 0,
    "message": { "role": "assistant", "content": "HTTP caching stores responses..." },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 21,
    "completion_tokens": 38,
    "total_tokens": 59,
    "prompt_tokens_details": { "cached_tokens": 0 }
  }
}
02 · StreamingSSE · data: [DONE]
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}
  }'

Set stream_options.include_usage to receive a final usage chunk.

Raw stream:

data: {"id":"chatcmpl-7f2c1b1e","object":"chat.completion.chunk","created":1762300000,"model":"minimax-m3.1-flash","choices":[{"index":0,"delta":{"role":"assistant","content":"HTTP "},"finish_reason":null}]}

data: {"id":"chatcmpl-7f2c1b1e","object":"chat.completion.chunk","created":1762300000,"model":"minimax-m3.1-flash","choices":[{"index":0,"delta":{"content":"caching "},"finish_reason":null}]}

data: {"id":"chatcmpl-7f2c1b1e","object":"chat.completion.chunk","created":1762300000,"model":"minimax-m3.1-flash","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"id":"chatcmpl-7f2c1b1e","object":"chat.completion.chunk","created":1762300000,"model":"minimax-m3.1-flash","choices":[],"usage":{"prompt_tokens":21,"completion_tokens":38,"total_tokens":59}}

data: [DONE]

Accumulate choices[0].delta.content; tool calls arrive as indexed fragments in delta.tool_calls.

03 · Thinking levelreasoning_effort

Send reasoning_effort: "low" | "medium" | "high" | "max". The gateway forwards it; max asks for the deepest budget the model offers, and the model decides how much to think. The reasoning text is returned as message.reasoning_content (some models use reasoning or thinking — those fields are passed through unchanged). In streaming it arrives as delta.reasoning_content before the answer.

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 is billed as completion tokens reported for the turn.

Response fields:

{
  "id": "chatcmpl-8a1d",
  "object": "chat.completion",
  "model": "minimax-m3.1-flash",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "reasoning_content": "The caller should retry idempotent requests with exponential backoff...",
      "content": "Use idempotency keys, exponential backoff with jitter..."
    },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 18,
    "completion_tokens": 120,
    "total_tokens": 138,
    "completion_tokens_details": { "reasoning_tokens": 64 }
  }
}
04 · Tool callingfull loop

Kitsune is a proxy: it forwards tools, tool_choice and parallel_tool_calls and returns the model's tool_calls. Your code executes the function, then sends the result back as a role:"tool" message.

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", "description": "City name"}},
          "required": ["city"],
          "additionalProperties": false
        }
      }
    }],
    "tool_choice": "auto"
  }'

Use strict JSON Schema; the model returns arguments as a JSON string.

First response — no content, tool calls instead:

{
  "id": "chatcmpl-9b2e",
  "object": "chat.completion",
  "model": "minimax-m3.1-flash",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": null,
      "tool_calls": [{
        "id": "call_abc123",
        "type": "function",
        "function": {
          "name": "get_time",
          "arguments": "{\"city\":\"Tashkent\"}"
        }
      }]
    },
    "finish_reason": "tool_calls"
  }],
  "usage": { "prompt_tokens": 42, "completion_tokens": 18, "total_tokens": 60 }
}

Then append this message and call again:

{
  "role": "tool",
  "tool_call_id": "call_abc123",
  "content": "{\"time\":\"2026-11-03T20:30:00Z\",\"timezone\":\"Asia/Tashkent\"}"
}
05 · Parallel tool callsparallel_tool_calls

Set parallel_tool_calls: true and the model may return several tool_calls in one message. Execute them (in parallel if you like), then append one role:"tool" message per tool_call_id before the next call.

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": "Check the time in Tashkent and Warsaw."}],
    "tools": [/* get_time, get_weather */],
    "tool_choice": "auto",
    "parallel_tool_calls": true
  }'
06 · Visionimage_url parts

Use a model with capabilities.vision. Pass content as an array of text and image_url parts. Both HTTPS URLs and base64 data URLs are accepted (model-dependent).

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"}}
      ]
    }]
  }'
07 · Structured outputjson_schema · json_object

Send response_format. json_schema with strict: true is the reliable form where the model supports it.

const API_KEY = process.env.KITSUNE_KEY;
const BASE = 'https://kitsune.cv/v1';

async function api(path, body) {
  const res = await fetch(BASE + path, {
    method: body ? 'POST' : 'GET',
    headers: {
      authorization: 'Bearer ' + API_KEY,
      'content-type': 'application/json'
    },
    body: body ? JSON.stringify(body) : undefined
  });
  if (!res.ok) throw new Error(await res.text());
  return res.json();
}

const completion = await api('/chat/completions', {
  model: 'minimax-m3.1-flash',
  messages: [{ role: 'user', content: 'Extract the invoice as JSON.' }],
  response_format: {
    type: 'json_schema',
    json_schema: {
      name: 'invoice',
      strict: true,
      schema: {
        type: 'object',
        properties: {
          vendor: { type: 'string' },
          total: { type: 'number' },
          currency: { type: 'string' }
        },
        required: ['vendor', 'total', 'currency'],
        additionalProperties: false
      }
    }
  }
});

const invoice = JSON.parse(completion.choices[0].message.content);
08 · Sampling controlsforwarded to the model
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": "Write a product name."}],
    "temperature": 0.9,
    "top_p": 0.95,
    "max_tokens": 64,
    "stop": ["\n\n"],
    "presence_penalty": 0.2,
    "frequency_penalty": 0.1,
    "seed": 42,
    "n": 1
  }'

Unknown/extra fields are forwarded unchanged. Support varies by model.

temperature · top_p · max_tokens · max_completion_tokens · stop · n · presence_penalty · frequency_penalty · logit_bias · logprobs · top_logprobs · seed · user · metadata · store · service_tier.

09 · System prompt & timegateway behaviour

Kitsune injects the admin main system prompt and the model's own system prompt before your messages, then appends the current UTC time (if the model has time context enabled). The time is appended at the end so the stable prompt prefix can still be cached. Your own role:"system" message is preserved after the injected text.

What the model receives (simplified):

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.

10 · Endpointscomplete
curl https://kitsune.cv/v1/models \
  -H "Authorization: Bearer $KITSUNE_KEY"

curl https://kitsune.cv/v1/models/minimax-m3.1-flash \
  -H "Authorization: Bearer $KITSUNE_KEY"

# public catalog, no key:
curl https://kitsune.cv/v1/catalog
methodpathauthnotes
POST/v1/chat/completionsBearerchat, streaming, tools, vision, reasoning
GET/v1/modelsBearermodels available to the key
GET/v1/models/{id}Bearerone model
GET/v1/catalogpublicprices, display names, capabilities
GET/v1/accountBearerbalance, accrued, keys, models
GET/v1/planBeareralias of /v1/account
GET/v1/usage?days=30Bearerdaily, per-model, per-key usage
GET/v1/keysBearerlist keys
POST/v1/keysBearercreate; secret returned once
PATCH/v1/keys/{id}Bearerlabel / spend cap
DELETE/v1/keys/{id}Bearerrevoke; send JSON content-type
11 · ErrorsJSON body
{
  "error": {
    "message": "balance is empty — top up to keep using the API",
    "type": "gateway_error",
    "code": "insufficient_balance"
  }
}
statuscodemeaning
400bad_requestinvalid body, model id, key request
401unauthorizedmissing / revoked key
402insufficient_balancetop up
403key_scopekey cannot use pay-as-you-go
403model_not_allowedrestricted model, no grant
404model_not_foundnot in /v1/models
404not_foundunknown key id
503no_routeevery route failed
12 · All parameterswhat Kitsune understands
fieldtypedefaultnotes
modelstring—public id from /v1/models
messagesarray—system, developer, user, assistant, tool; content string or parts
streamboolfalseSSE; ends with data: [DONE]
stream_optionsobject—{"include_usage":true} adds a final usage chunk
temperaturenumbermodel0–2
top_pnumbermodelnucleus sampling
max_tokensintmodeloutput cap
max_completion_tokensintmodelnewer alias; forwarded
stopstring / array—stop sequence(s)
nint1number of choices
presence_penaltynumber0−2 to 2
frequency_penaltynumber0−2 to 2
logit_biasobject—token id → bias
logprobsboolfalsereturn log probabilities
top_logprobsint—0–20 with logprobs
seedint—best-effort determinism
toolsarray—JSON-Schema function tools
tool_choicestring / objectautoauto, none, required, or a named function
parallel_tool_callsboolmodelallow multiple tool calls per turn
response_formatobjecttexttext, json_object, json_schema
reasoning_effortstringmodellow · medium · high · max
userstring—end-user identifier; forwarded
metadataobject—forwarded
storebool—forwarded; model-dependent
service_tierstring—forwarded; model-dependent

Any other JSON field is forwarded unchanged. Kitsune itself does not execute tools, browse, or store prompts.

13 · Response schemanon-stream & stream
fieldtypenotes
idstringcompletion id
objectstringchat.completion or chat.completion.chunk
modelstringKitsune public model id
choices[].indexintchoice position
choices[].message.rolestringassistant
choices[].message.contentstring / nullanswer text; null when a tool call is returned
choices[].message.reasoning_contentstringthinking text when the model returns it
choices[].message.tool_callsarrayid, type, function.name, function.arguments (JSON string)
choices[].finish_reasonstringstop, length, tool_calls, content_filter
usage.prompt_tokensintinput tokens, including cached and injected system text
usage.completion_tokensintoutput tokens, including thinking tokens where reported
usage.total_tokensintsum
usage.prompt_tokens_details.cached_tokensintcache hits; billed at the cache price
usage.completion_tokens_details.reasoning_tokensintreported thinking tokens (optional)
system_fingerprintstringfingerprint when supplied

Streaming: choices[0].delta carries content, reasoning_content and indexed tool_calls; a final usage chunk appears when stream_options.include_usage is set.

14 · Headersobservability
headervaluemeaning
x-kitsune-billingpaygbilling rail
retry-aftersecondspresent when a rate limit is surfaced

No CORS headers are sent. Use the API from a server or the Kitsune console.

15 · HTTP setupno SDK required

No SDK is required. Any HTTP client works: send Authorization: Bearer kts-… and a JSON body to https://kitsune.cv/v1. The examples below use fetch, requests, curl and PHP’s cURL extension — no OpenAI package in any language.

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

All examples target the same OpenAI-compatible schema over plain HTTPS.

Runtime notes: JavaScript uses the built-in fetch; Python examples use requests (pip install requests); PHP examples need the curl extension. No OpenAI package is used anywhere.

16 · Limits & retrieshonest limits
  • • The gateway allows 600 requests/min per account. Above that it returns 429 rate_limited; wait for the window to reset before retrying. Balance, key scope and spend caps are enforced on top.
  • • Request bodies are capped at 25 MB (including base64 images); larger requests return 413. Prompts should stay within the model's context window.
  • • A 429 may also come from the model. Where another route is wired the gateway may fail over, otherwise the status is surfaced. Back off and retry.
  • • A zero balance returns 402 before the request is sent. Failed calls are not billed.
  • • Aborted streams are billed for the usage reported by the service.
  • • Prompt and completion bodies are not stored — zero data retention. Request metadata is kept for billing and the usage page.