Responses
Create a model response with the OpenAI Responses format. This is a stateless subset built for coding agents such as Codex, and the official OpenAI SDKs work once you point the base URL at https://kenari.id/v1. For Codex configuration, see Codex.
POST /v1/responses
Authenticate with Authorization: Bearer kn-.... See Authentication and API keys.
Stateless by design
Section titled “Stateless by design”kenari stores nothing between requests, so send the whole conversation in input every time.
- A
previous_response_idother thannullreturns 400. storeis accepted and ignored.- There are no endpoints to retrieve, list or delete a response.
Request
Section titled “Request”| Field | Type | Required | Description |
|---|---|---|---|
model | string | Yes | A catalog id, a :free id, a route name or a byok/ id. The same values as in Chat completions. |
input | string or array | Yes | A string is one user message. An array holds input items. It must not be empty. |
instructions | string | No | System instructions. Merged with any leading developer items. |
tools | array | No | Flat function tools, each with type set to function, plus name, description and parameters. A function tool without name returns 400. kenari server tools such as {"type": "kenari:web_search"} also work. Every other tool type is dropped without an error. See Function calling and Server tools. |
tool_choice | string or object | No | auto, none, required or {"type": "function", "name": "get_weather"} to force one function. Anything else returns 400. |
reasoning | object or null | No | {"effort": "low"}. Levels are none, minimal, low, medium, high, xhigh and max. summary is accepted and ignored. See Reasoning. |
max_output_tokens | integer | No | Upper limit on generated tokens, reasoning included. Default 65,536. |
temperature | number | No | Sampling temperature. |
top_p | number | No | Nucleus sampling. |
stream | boolean | No | Set true to receive Server-Sent Events. See Streaming. |
text | object | No | {"format": {"type": "text"}} (the default), {"format": {"type": "json_object"}}, or {"format": {"type": "json_schema", "name": "...", "schema": {...}, "strict": true}} for JSON output. Any other type returns 400. See Structured output. |
prompt_cache_key | string | No | A stable id for one conversation, so follow-up requests reuse its cached prefix. Up to 512 bytes. Longer or blank values are ignored. See Prompt caching. |
previous_response_id | string or null | No | Must be absent or null. |
store | boolean | No | Accepted and ignored. |
include | array | No | Accepted and ignored. |
metadata | object | No | Accepted and ignored. |
parallel_tool_calls | boolean | No | Accepted and ignored. |
kenari does not read the routing fields provider, models and route, or plugins, on this endpoint. It never passes them on, whether the request runs on your own key or on any other model.
Input items
Section titled “Input items”| Item | Fields | Notes |
|---|---|---|
| Message | role, content | role is system, developer, user or assistant. type may be omitted. content is a string or an array of parts. |
| Function call | type set to function_call, call_id, name, arguments | A call the model made earlier. arguments is a JSON string. |
| Function result | type set to function_call_output, call_id, output | The result for a call. output is a string or an array of text parts. |
| Reasoning | type set to reasoning, summary (an array) | Earlier reasoning, handed back to the model. |
Leading system and developer messages merge into the system prompt. Unknown item types are dropped without an error, and any other role returns 400.
Message parts are input_text and output_text, plus input_image with an image_url string, which is accepted only in user and assistant messages (in a developer message it returns 400). An input_file part returns 400. For documents, use Chat completions: a file part works on any model. See Images and files as input and Read documents.
Response
Section titled “Response”The response is a response object. Its output array holds the items the model produced, in order.
{ "id": "resp_4f1c9a2e7b3d5a60", "object": "response", "created_at": 1790000000, "status": "completed", "model": "step-3-7-flash:free", "output": [ { "type": "message", "id": "msg_1", "role": "assistant", "content": [{ "type": "output_text", "text": "Hello! How can I help?" }] } ], "usage": { "input_tokens": 9, "output_tokens": 12, "total_tokens": 21, "input_tokens_details": { "cached_tokens": 0 } }, "text": { "format": { "type": "text" } }}| Output item | Description |
|---|---|
reasoning | The model’s reasoning as one summary_text entry. First in the array, and only present when the model reasoned. |
message | The answer, in an output_text part. |
function_call | A function the model wants you to run: call_id, name and arguments as a JSON string. Run it and send a function_call_output item with the same call_id in the next request. |
status is completed, or incomplete with incomplete_details.reason set to max_output_tokens when the limit cut the answer short. text.format echoes the format you asked for, {"type": "text"} by default. usage.input_tokens_details.cached_tokens counts input served from cache and is part of input_tokens.
After you run a function, send a follow-up request that carries the earlier call and its result in input:
{ "model": "step-3-7-flash:free", "input": [ { "role": "user", "content": "What is the weather in Jakarta?" }, { "type": "function_call", "call_id": "call_1", "name": "get_weather", "arguments": "{\"city\": \"Jakarta\"}" }, { "type": "function_call_output", "call_id": "call_1", "output": "31 C and sunny" } ], "tools": [ { "type": "function", "name": "get_weather", "description": "Get the current weather for a city.", "parameters": { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] } } ]}Streaming
Section titled “Streaming”Set "stream": true to receive the response as Server-Sent Events. The event sequence is in Streaming.
Examples
Section titled “Examples”curl https://kenari.id/v1/responses \ -H "Authorization: Bearer $KENARI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "step-3-7-flash:free", "instructions": "You answer briefly.", "input": "Hello!" }'Python
Section titled “Python”import osfrom openai import OpenAI
client = OpenAI(base_url="https://kenari.id/v1", api_key=os.environ["KENARI_API_KEY"])
response = client.responses.create( model="step-3-7-flash:free", instructions="You answer briefly.", input="Hello!",)print(response.output_text)JavaScript
Section titled “JavaScript”import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://kenari.id/v1", apiKey: process.env.KENARI_API_KEY,});
const response = await client.responses.create({ model: "step-3-7-flash:free", instructions: "You answer briefly.", input: "Hello!",});console.log(response.output_text);Billing
Section titled “Billing”Each request is billed per token at the model’s Input, Output, Cache read and Cache write prices. :free models cost nothing. See How billing works.
Errors
Section titled “Errors”Errors use the OpenAI envelope: an error object with message, type, param and code. Only the errors specific to this endpoint are listed here. The rest, such as model_not_found, context_length_exceeded and subscribers_only, are in Errors.
| Status | Code | When |
|---|---|---|
| 400 | bad_request | input is missing or empty. previous_response_id is not null. text.format.type is not text, json_object or json_schema. A message has an unknown role. An input_file part is present. A function tool has no name. tool_choice is not one of the supported forms. |
| 400 | bad_request | The model is not a chat model, such as an image or speech model. The message names the endpoint to use instead. |