# Models and pricing

List every model you can call, with what it can do and what it costs. The list is public, so you do not need an API key, and it changes over time, so read it from the endpoint instead of hardcoding ids.

`GET /v1/models`

## Request

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `modality` | string | No | List one specialized kind of model instead of the default list. One of `embedding`, `rerank`, `moderation` or `systemone`. See [Filter by modality](#filter-by-modality). |

Without `modality`, the list holds chat, image, text to speech, transcription, music and video models. It does not hold embedding, rerank, moderation or System One models. Each entry's `endpoints` says which API it serves, so a tool that only wants chat models should keep the entries whose `endpoints` include `chat`. Music models are in the default list too, and there is no `?modality=music` filter. The `kenari/auto` entry, when listed, has no `endpoints`, because the model that serves a request differs each time. A missing `endpoints` means no metadata is recorded, not that the model lacks a capability, so the examples below treat it as an empty list.

## Response

The response is an OpenAI-style list: `{"object": "list", "data": [...]}`. This is one entry:

```json
{
  "id": "step-3-7-flash",
  "object": "model",
  "owned_by": "stepfun",
  "name": "step-3-7-flash",
  "context_length": 262144,
  "max_output_tokens": 65536,
  "modalities": { "input": ["text", "image", "video"], "output": ["text"] },
  "endpoints": ["chat"],
  "protocols": ["openai-chat", "openai-responses", "anthropic-messages"],
  "reasoning": true,
  "reasoning_options": ["low", "medium", "high"],
  "reasoning_toggle": false,
  "tool_call": true,
  "pricing": {
    "input": 4200000000,
    "output": 24000000000,
    "cache_read": 840000000,
    "cache_write": null,
    "free": false,
    "currency": "IDR",
    "unit": "micro_idr_per_1m_tokens"
  },
  "pricing_lines": [
    { "endpoint": "chat", "billable": "input", "unit": "token_1m", "variant": null, "micro_idr": 4200000000 },
    { "endpoint": "chat", "billable": "output", "unit": "token_1m", "variant": null, "micro_idr": 24000000000 },
    { "endpoint": "chat", "billable": "cache_read", "unit": "token_1m", "variant": null, "micro_idr": 840000000 }
  ],
  "sunset_at": null
}
```

A field kenari has no value for is left out instead of being set to `null`. `id`, `object`, `owned_by`, `name` and `sunset_at` are always there. The rest depends on the kind of entry, as the tables below show.

| Field | Type | Description |
| --- | --- | --- |
| `id` | string | The id to send as `model`. |
| `object` | string | Always `model`. |
| `owned_by` | string | The vendor that makes the model, for example `anthropic`. |
| `name` | string | A display name. Every entry has one. When a model has no separate display name, `name` is the same as `id`. |
| `context_length` | integer | The context window in tokens. |
| `max_input_tokens` | integer | The most input tokens one request can carry, when the model limits input below its context window. A request with a larger input is refused with `context_length_exceeded`. Left out when the context window is the only limit. Not on `?modality=` entries. |
| `max_output_tokens` | integer | The longest output the model can produce in one response, in tokens. Left out when unknown, which means nothing is recorded, not that the model has no limit. Not on `?modality=` entries. |
| `modalities` | object | `input` and `output` arrays. The values are `text`, `image`, `pdf`, `file`, `audio` and `video`. `pdf` means the model reads PDFs itself. Every chat model lists `file`, because kenari reads attached documents for any model. Only the `native` engine depends on the model reading files itself. See [Images and files as input](/en/docs/vision). |
| `endpoints` | array | The APIs the model serves: `chat`, `images`, `videos`, `audio_speech`, `audio_transcription`, `music`, `embeddings`, `rerank`, `moderations` or `systemone`. |
| `protocols` | array | The API formats you can call the model with: `openai-chat` for [Chat completions](/en/docs/chat), `openai-responses` for [Responses](/en/docs/responses) and `anthropic-messages` for [Messages](/en/docs/messages). Only on chat models and `kenari/auto`, and every one of them lists all three. Left out on image, speech, video, music and `?modality=` entries. |
| `reasoning` | boolean | Whether the model produces reasoning. See [Reasoning](/en/docs/reasoning). |
| `reasoning_options` | array | The levels `reasoning_effort` accepts for this model, such as `low`, `medium` and `high`. |
| `supported_reasoning_levels` | array | Same values as `reasoning_options`, in the field name some agent gateways read. Omitted when the model has no effort levels. |
| `reasoning_toggle` | boolean | Present on entries of the default list, but not on `?modality=` entries. `true` when you can switch reasoning on or off per request, `false` when you cannot. |
| `tool_call` | boolean | Whether the model supports function calling. See [Function calling](/en/docs/function-calling). |
| `pricing` | object | The price card. See [Prices in the list](#prices-in-the-list). |
| `pricing_lines` | array | One entry per billable item. Left out when the model has none, as on most `:free` models. |
| `sunset_at` | integer or null | The time, in epoch seconds, when kenari plans to stop serving the model. `null` when none is planned. |
| `modality` | string | Only on `?modality=` results. The kind of model. |

Most media fields below are left out when no value is recorded for the model. A missing field means nothing is recorded, not that the model has no limit.

| Field | Applies to | Description |
| --- | --- | --- |
| `voices` | text to speech | Voice names, case sensitive. The first one is the default when a request has no `voice`. |
| `formats` | text to speech, music | Audio formats the model can return, such as `mp3`. The first one is the default when a request has no `response_format`. On a music model it also tells the two kinds of music model apart, see [Music](/en/docs/music). |
| `max_input_chars` | text to speech | The longest `input` the model accepts, in characters. A longer one returns 400. |
| `max_prompt_chars` | music | The longest `prompt`, in characters. |
| `max_lyrics_chars` | music | The longest `lyrics`, in characters. |
| `max_duration_secs` | music | The longest song the model makes, in seconds. For information only, because you cannot request a length. |
| `video_durations` | video | The clip lengths you can request, in seconds. |
| `video_resolutions` | video | The resolutions you can request, such as `720p`. |
| `max_reference_images` | video | The most reference images one request can carry in `input_images`. `0` means the model takes none. Left out when no figure is published for the model, which does not mean it takes none. See [Reference images](/en/docs/videos#reference-images). |
| `video_extensions` | video | `true` when the model can extend a clip through `POST /v1/videos/extensions`. Present only when true, and left out otherwise. |

For how to use these values, see [Text to speech](/en/docs/audio-speech), [Music](/en/docs/music) and [Videos](/en/docs/videos).

### Prices in the list

`pricing` and `pricing_lines` give prices in micro-Rupiah, which is Rupiah times 1,000,000. Divide by 1,000,000 to get Rupiah. In the example above, an `input` of `4200000000` is Rp 4,200 per 1 million tokens.

| `pricing` field | Description |
| --- | --- |
| `input`, `output`, `cache_read`, `cache_write` | The price per 1 million tokens. `null` when the model has no such price. |
| `free` | `true` when the model costs nothing to call. Prices that are still shown on a free model are the list price of its paid version, and are not charged. |
| `currency`, `unit` | Always `IDR` and `micro_idr_per_1m_tokens`. |

Models that are not priced per token, such as image, speech and video models, show `0` for `input` and `output`. Read their price from `pricing_lines` instead. Each line has these fields:

| Field | Description |
| --- | --- |
| `endpoint` | The API the price belongs to, such as `chat` or `audio_speech`. |
| `billable` | What is billed: `input`, `output`, `cache_read`, `cache_write`, `input_audio`, `output_audio`, `output_image` or `output_video`. |
| `unit` | `token_1m` for 1 million tokens, `1k_chars` for 1,000 characters, `second`, `image` or `song` (music). |
| `variant` | A tier of the price, such as a video resolution like `720p`. `null` when there is none. |
| `micro_idr` | The price in micro-Rupiah for one `unit`. |

### Retiring models

A model with a `sunset_at` keeps being listed and served until that time. After it passes, the model leaves the list. Requests for its id are then served by a successor model when kenari has set one, and rejected with `model_not_found` when it has not. If you keep a copy of the list, check this field so you can move to another model before the date. Scheduled retirements also appear on [Model changes](/en/docs/model-changes).

Accounts with a verified email that called a model in the last 30 days get an email when its retirement is scheduled, and again a day before it retires.

### `x-kenari-model-forwarded`

This header is present only when you call a model that kenari has retired and that model has a successor. The request is served by the successor and billed at the successor's price, the response `model` field names the successor, and the header records the forward as `<model you sent>-><model that served>`:

```text
x-kenari-model-forwarded: deepseek-v4-flash->deepseek-v4-1-flash
```

It appears on Chat completions, Messages, Responses and the media endpoints (images, audio, speech, transcription, music, video, embeddings, rerank and moderations). Browsers cannot read it from JavaScript, because CORS does not expose it. Server-side clients and SDKs can.

## Filter by modality

Embedding, rerank, moderation and System One models are listed only when you ask for them with `?modality=`.

| `modality` | Lists | Example ids | Used by |
| --- | --- | --- | --- |
| `embedding` | Embedding models | `bge-m3`, `text-embedding-3-small` | [Embeddings](/en/docs/embeddings) |
| `rerank` | Rerank models | `bge-reranker-base` | [Rerank](/en/docs/rerank) |
| `moderation` | Moderation models | ids from this call | [Moderations](/en/docs/moderations) |
| `systemone` | System One models | `clef:free` | [System One](/en/docs/systemone) |

```bash
curl "https://kenari.id/v1/models?modality=embedding"
```

The entries carry `id`, `object`, `owned_by`, `name`, `sunset_at`, `modality` and `pricing`, and `endpoints`, `context_length` and `pricing_lines` when recorded. They have no `reasoning_toggle`, and `pricing` has only `input`, `free`, `currency` and `unit`, with `pricing.input` holding the price per 1 million tokens. A `modality` value that is not one of the four, including an empty one, returns 400.

```json
{
  "error": {
    "message": "unknown modality 'foo'. Accepted values: embedding, rerank, moderation, systemone.",
    "type": "bad_request",
    "param": null,
    "code": "bad_request"
  }
}
```

## Free versions

A model's free version has the same id with a `:free` suffix, is listed with `pricing.free` set to `true`, and costs nothing. See [Free models](/en/docs/free-models).

## Examples

### curl

```bash
curl https://kenari.id/v1/models
```

### Python

```python
import requests

models = requests.get("https://kenari.id/v1/models").json()["data"]
chat_models = [m["id"] for m in models if "chat" in m.get("endpoints", [])]
print(chat_models)
```

### JavaScript

```js
const response = await fetch("https://kenari.id/v1/models");
const { data } = await response.json();
const chatModels = data.filter((m) => (m.endpoints ?? []).includes("chat")).map((m) => m.id);
console.log(chatModels);
```

## Public price list

`GET /api/public/pricing` is a second public feed, made for price pages. It needs no key and returns `{"items": [...], "usd_idr_rate": ..., "free_tier": {...}}`.

- Each item has `model`, `provider` (the vendor), `free`, `subscriber_only`, `endpoints`, `pricing_lines`, `input_modalities`, `output_modalities`, `sunset_at` and `upcoming_price`, the next scheduled price change, or `null` when there is none.
- The per-token prices are named `price_in_micro_idr_per_1m`, `price_out_micro_idr_per_1m`, `price_cached_micro_idr_per_1m` and `price_cache_write_micro_idr_per_1m`, and `image_micro_idr_per_image` is the price of one image. `market_in_micro_idr_per_1m` and `market_out_micro_idr_per_1m` hold a reference market price, only when kenari's price is lower.
- `subscriber_only` is `true` when only accounts with an active subscription can call the model. See [Subscriptions](/en/docs/subscriptions).
- It lists every model that is for sale, including the `:free` versions and older versions of a family. It leaves out embedding, rerank, moderation and System One models.
- `usd_idr_rate` is the exchange rate in use, and `free_tier` describes what a free account gets. For free-tier limits, read [Free models](/en/docs/free-models).

To find what you can call and what it costs, use `GET /v1/models`.

To see what changed over time, read [Model changes](/en/docs/model-changes).

## Errors

| Status | Code | When |
| --- | --- | --- |
| 400 | `bad_request` | `modality` is not `embedding`, `rerank`, `moderation` or `systemone`. |

The other errors are in [Errors](/en/docs/errors).

## See also

- [Chat completions](/en/docs/chat)
- [Free models](/en/docs/free-models)
- [Reasoning](/en/docs/reasoning)
- [How billing works](/en/docs/billing)

## Prices

The table below is loaded live from `GET /v1/models`, so it shows what is billed today. Token prices are in Rupiah per 1 million tokens. Image, speech, video and the other models that are not priced per token have their own tables underneath, each with its unit. For how a request is billed, see [How billing works](/en/docs/billing).
