On this page

KiwiMate API reference

The KiwiMate API gives your apps the same models that power KiwiMate, through a single endpoint that follows OpenAI's chat.completions format. If your code already talks to OpenAI, you usually only need to change the base URL, the API key and the model name.

Base URL
https://gznrhppouxwpfihlfgpb.supabase.co/functions/v1/api-chat-completions

Send requests to the base URL itself. Requests to {base URL}/chat/completions (the path the OpenAI SDKs add) reach the same endpoint, so either works. All prices are in New Zealand dollars.

Quickstart

1. Create a key. Sign in and open the developer console, then click Create new key. Your first key comes with $5 NZD of trial credit.

2. Store it in an environment variable. Every example in these docs reads it from KIWIMATE_API_KEY.

Terminal
export KIWIMATE_API_KEY="sk-km-..."

# Optional: the OpenAI SDK for your language
pip install openai   # Python
npm install openai   # Node.js

3. Make a request.

curl https://gznrhppouxwpfihlfgpb.supabase.co/functions/v1/api-chat-completions \
  -H "Authorization: Bearer $KIWIMATE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kiwimate-medium-1-0",
    "messages": [
      {
        "role": "user",
        "content": "Kia ora! Suggest three day trips from Wellington."
      }
    ]
  }'

Prefer to experiment first? The playground lets you try every model and setting in the browser, then copy the matching code.

Authentication

Authenticate every request with your API key as a bearer token. Keys start with sk-km-.

Authorization: Bearer sk-km-...
  • Keys are shown once, when you create them. KiwiMate only stores a hash, so a lost key can't be recovered. Revoke it and create a new one.
  • Revoking a key in the console takes effect immediately. Revoked, unknown or missing keys get 401 authentication_error.
  • All keys on your account spend from the same credit balance. Create one key per app or environment so you can revoke them independently and see per-key spend in the console.

Keep keys secret

Anyone with your key can spend your credit. Call the API from your own server, and never put a key in browser JavaScript, a mobile app bundle, or a public repository.

Models

Pass one of these ids as model. Prices are NZD per million tokens.

ModelVisionThinkingMax outputInputOutput
kiwimate-mini-1-0YesNo1,024$1$3
kiwimate-small-1-0YesYes1,024 (3,072 thinking)$2$5
kiwimate-medium-1-0YesYes1,024 (3,072 thinking)$4$8
kiwimate-large-1-0YesYes1,024 (3,072 thinking)$5$10
  • Any other model id returns 400 invalid_request_error, including the retired kiwimate-mini-preview and kiwimate-small-preview.
  • Image generation models (KiwiMate-Image-Mini-1.0, KiwiMate-Image-Fast-1.0, KiwiMate-Image-Pro-1.0) aren't available through the API yet. These docs will cover them once they are.
  • Model sizes, licences and open-weight downloads are on the models page.

List models

GET {base URL}/models returns the models above in OpenAI's format, so client.models.list() works and tools that check your key or fetch the model list on startup can connect. It needs a valid key, but it's free and doesn't count toward your rate limit.

Request
GET https://gznrhppouxwpfihlfgpb.supabase.co/functions/v1/api-chat-completions/models
Authorization: Bearer sk-km-...
200 OK
{
  "object": "list",
  "data": [
    { "id": "kiwimate-mini-1-0", "object": "model", "created": 1787097600, "owned_by": "kiwimate" },
    { "id": "kiwimate-small-1-0", "object": "model", "created": 1787097600, "owned_by": "kiwimate" },
    { "id": "kiwimate-medium-1-0", "object": "model", "created": 1787097600, "owned_by": "kiwimate" },
    { "id": "kiwimate-large-1-0", "object": "model", "created": 1787097600, "owned_by": "kiwimate" }
  ]
}

Chat completions

Creates a model response for the given conversation.

Request
POST https://gznrhppouxwpfihlfgpb.supabase.co/functions/v1/api-chat-completions
Authorization: Bearer sk-km-...
Content-Type: application/json

Request body

ParameterTypeDescription
modelstring, requiredA model id from the models table.
messagesarray, requiredThe conversation so far, oldest first. Must be non-empty, and the last message must have role: "user". See Messages.
streambooleanDefault false. Stream the reply as server-sent events. See Streaming.
max_completion_tokens
or max_tokens
integer > 0Upper limit on generated tokens. Send either name. max_tokens is the older one, and if you send both, max_completion_tokens is used. Values above the model's ceiling (1,024, or 3,072 with thinking on) are lowered to the ceiling instead of being rejected. Defaults to the ceiling.
temperaturenumber, 0–2Default 0.7. Lower is more focused, higher is more varied. 0 is effectively deterministic.
thinkingbooleanKiwiMate extension. Default false. Ask the model to reason before answering. See Thinking.

Other OpenAI parameters (for example tools, response_format, n, top_p, stop, seed and stream_options) are accepted but ignored.

Messages

Each message is { role, content }. role is user, assistant or system. content is either a string or an array of content parts (text and image_url, see Vision). Other roles such as developer or tool return 400.

System messages and history

KiwiMate models always run with KiwiMate's own system prompt, a friendly New Zealand assistant that keeps replies concise and warm. system messages are accepted for compatibility but are not sent to the model. To steer behaviour, put your instructions in the first user message. Only the most recent 20 user and assistant messages are sent to the model, and older ones are dropped.

Vision

Every model accepts images alongside text. Send them as image_url content parts containing a base64 data:image/… URL. Remote https:// image URLs aren't supported, so download the image and encode it first.

curl https://gznrhppouxwpfihlfgpb.supabase.co/functions/v1/api-chat-completions \
  -H "Authorization: Bearer $KIWIMATE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kiwimate-medium-1-0",
    "messages": [
      {
        "role": "user",
        "content": [
          {
            "type": "text",
            "text": "What native bird is in this photo?"
          },
          {
            "type": "image_url",
            "image_url": {
              "url": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."
            }
          }
        ]
      }
    ]
  }'
  • At most 4 images per message.
  • Each data URL must be under 3,000,000 characters, about 2.2 MB of image data.
  • Images count toward input tokens, so larger images cost more. Resize them to the smallest size that still works for your task.

Thinking

Set thinking: true to have the model reason step by step before it answers. Useful for maths, logic and multi-step questions. Supported by kiwimate-small-1-0, kiwimate-medium-1-0 and kiwimate-large-1-0. Sending it to a model without thinking support returns 400.

curl https://gznrhppouxwpfihlfgpb.supabase.co/functions/v1/api-chat-completions \
  -H "Authorization: Bearer $KIWIMATE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kiwimate-large-1-0",
    "messages": [
      {
        "role": "user",
        "content": "A ferry leaves Wellington at 8:45am and takes 3h 30m to reach Picton. When does it arrive?"
      }
    ],
    "thinking": true,
    "max_tokens": 2048
  }'
  • The reasoning comes back in message.reasoning_content, separate from message.content. When streaming it arrives in delta.reasoning_content.
  • Thinking tokens are billed as output tokens. The output ceiling rises to 3,072 tokens to make room for them.
  • If the model uses up its token limit while still thinking, content holds a short note saying it ran out of room and finish_reason is "length". Raise the limit or turn thinking off for that request.

Streaming

With stream: true, the response is text/event-stream. Each event is a data: line holding a chat.completion.chunk, and the stream ends with data: [DONE]. Tokens arrive as the model generates them, and every chunk in a response shares the same id.

curl -N https://gznrhppouxwpfihlfgpb.supabase.co/functions/v1/api-chat-completions \
  -H "Authorization: Bearer $KIWIMATE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kiwimate-small-1-0",
    "messages": [
      {
        "role": "user",
        "content": "Write a haiku about kiwifruit."
      }
    ],
    "stream": true
  }'
Raw event stream
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"kiwimate-small-1-0","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"kiwimate-small-1-0","choices":[{"index":0,"delta":{"content":"Fuzzy"},"finish_reason":null}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"kiwimate-small-1-0","choices":[{"index":0,"delta":{"content":" brown"},"finish_reason":null}]}

…

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"kiwimate-small-1-0","choices":[{"index":0,"delta":{"content":""},"finish_reason":"stop"}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","model":"kiwimate-small-1-0","choices":[],"usage":{"prompt_tokens":31,"completion_tokens":19,"total_tokens":50}}

data: [DONE]
  • The last chunk before [DONE] carries usage and has an empty choices array. Check that choices[0] exists before reading it.
  • With thinking on, reasoning streams in delta.reasoning_content first, and delta.content may be an empty string until the answer starts.
  • You're charged once per request, from the model's token count. Disconnecting early doesn't cancel the charge: you pay for what the model processed up to that point, meaning the prompt plus the tokens generated so far (which can include a few produced just after you disconnected). In the rare case the model hasn't reported a count yet, the charge is estimated at about 4 characters per token.

Response format

Non-streaming requests return a standard chat.completion object.

200 OK
{
  "id": "chatcmpl-3f1c2a9e-8d7b-4c1e-9a55-0e6f7b2d1c44",
  "object": "chat.completion",
  "created": 1790000000,
  "model": "kiwimate-medium-1-0",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Kia ora! Here are three great day trips from Wellington: ...",
        "reasoning_content": "..."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 38,
    "completion_tokens": 212,
    "total_tokens": 250
  }
}
  • reasoning_content is only present when you asked for thinking and the model produced some.
  • usage holds exact token counts reported by the model server. These are the numbers you're billed on.
  • finish_reason is "length" when the reply was cut short by the token limit, and "stop" when the model finished on its own. Streaming responses report it the same way, in the last chunk that has a choice.

Errors

Errors use OpenAI-style status codes and bodies, so the OpenAI SDKs' built-in error classes and retries work as expected.

Error body
{
  "error": {
    "type": "invalid_request_error",
    "message": "temperature must be a number between 0 and 2"
  }
}
Statuserror.typeMeaningWhat to do
400invalid_request_errorBad JSON, unknown model, invalid messages, unsupported thinking, too many or too-large images, or out-of-range parameters.Fix the request. The message says what's wrong.
401authentication_errorMissing, malformed, unknown or revoked API key.Check the Authorization header and key.
405invalid_request_errorMethod other than POST, apart from GET on /models.Use POST, or GET for /models.
429rate_limit_exceededMore than 60 requests this minute on this key.Wait for the next minute, then retry with backoff.
429insufficient_quotaYour credit balance is zero or below.Add credit. Retrying won't help.
502server_errorThe model server returned an error or an empty reply.Retry once or twice with backoff.
503server_errorThe model was still starting up after 45 seconds.Retry after the Retry-After header (30 s).

Failed requests are never charged. Both 429 types share a status code, so check error.type to tell them apart. The OpenAI SDKs retry every 429 automatically, including insufficient_quota, so an empty balance surfaces after a few seconds of retries rather than straight away.

Cold starts

Models scale down when idle. The first request after a quiet spell waits while the model wakes up, up to 45 seconds, and returns 503 if it's still not ready. Set your HTTP client's timeout above 60 seconds so you don't give up early.

Python
import os
from openai import OpenAI, RateLimitError

# The OpenAI SDK retries 429 and 5xx responses for you (2 retries by default,
# honouring Retry-After). A few more retries help ride out a cold start.
client = OpenAI(
    api_key=os.environ["KIWIMATE_API_KEY"],
    base_url="https://gznrhppouxwpfihlfgpb.supabase.co/functions/v1/api-chat-completions",
    max_retries=4,
)

try:
    completion = client.chat.completions.create(
        model="kiwimate-medium-1-0",
        messages=[{"role": "user", "content": "Kia ora!"}],
    )
except RateLimitError as err:
    if err.type == "insufficient_quota":
        print("Out of credit. Top up in the KiwiMate developer console.")
    else:
        raise

Rate limits

  • Each key can make 60 requests per minute. The count resets at the start of every clock minute, so it isn't a rolling 60-second window.
  • Every chat completion request with a valid key counts, including ones later rejected for a bad body or an empty balance. Listing models doesn't count.
  • Limits apply per key. Need more? Email hello@kiwimate.net and tell us about your use case.

Pricing & billing

The API is pay-as-you-go from a prepaid NZD credit balance. There are no subscriptions or monthly minimums.

  • Per-token pricing. Each request costs input tokens × input price + output tokens × output price, using the per-million prices in the models table.
  • Rounded up to the cent. Each request's cost is rounded up to a whole cent, so every successful request costs at least $0.01. For many small requests, this minimum is most of the cost.
  • Trial credit. Your first API key adds $5 NZD of credit to your account, once per account.
  • Top-ups. Add $1–$1,000 NZD at a time by card from the console. Payments are processed by Stripe.
  • Balance checks. Requests are accepted while your balance is above zero and charged when they finish, so your final request can take the balance slightly below zero. Later requests get 429 insufficient_quota until you top up.
  • Tracking spend. The console shows daily spend, usage by model and key, and every charge, which you can download as CSV.

OpenAI SDKs

The official OpenAI libraries for Python and Node.js work unchanged. Set the base URL to KiwiMate's and use your KiwiMate key.

  • Python: OpenAI(api_key=..., base_url="https://gznrhppouxwpfihlfgpb.supabase.co/functions/v1/api-chat-completions"). Pass KiwiMate-only fields through extra_body={"thinking": True}, and read getattr(message, "reasoning_content", None).
  • Node.js: new OpenAI({ apiKey, baseURL: "https://gznrhppouxwpfihlfgpb.supabase.co/functions/v1/api-chat-completions" }). In TypeScript, thinking and reasoning_content aren't in the SDK's types, so add a cast or a // @ts-expect-error comment where you use them.
  • client.models.list() returns the API's models, and chat calls work whether they set max_completion_tokens or the older max_tokens.
  • Other OpenAI-compatible tools that let you set a custom base URL and model name should work too, as long as they only use the features on this page.

Best practices

  • Keep keys on the server, in environment variables or a secrets manager. Rotate a key if you think it has leaked.
  • Stream responses in chat interfaces so people see the reply as it's written instead of waiting for the whole thing.
  • Set max_tokens to what you actually need, to cap both cost and latency.
  • Retry 429 rate_limit_exceeded, 502 and 503 with exponential backoff. Don't retry 400, 401 or insufficient_quota.
  • Pick the smallest model that does the job well. Mini is ideal for short, simple tasks, and Large for the hardest ones.
  • Check the console regularly, and top up before your balance runs out if you run anything in production.

Current limitations

The KiwiMate API is young. These are the things it doesn't do yet:

  • No custom system prompts. The KiwiMate persona is always applied (see Messages).
  • No tool/function calling, JSON mode, logprobs or multiple choices (n).
  • No embeddings endpoint, and no single-model lookup (GET /models/{id}). The only endpoints are chat completions and listing models.
  • No image generation through the API yet.
  • Only the last 20 messages of a conversation reach the model.
  • Replies stop early if the model writes the literal text User: or Assistant:, which are used internally as stop sequences.

Have a request or found a bug? Tell us on the Ideas & Progress board or report an issue.