# Responses API
> POST /v1/responses: OpenAI’s Responses API and the Agents SDK, stateless, for every model.
```text
POST https://api.clonvo.com/v1/responses
```
Creates a model response in the format of OpenAI’s [Responses API](https://platform.openai.com/docs/api-reference/responses). Use it with `client.responses` in the OpenAI SDKs, with the [OpenAI Agents SDK](#openai-agents-sdk), and with other tools that default to the Responses API.
It works with every model in the [catalogue](https://clonvo.com/models.md). Clonvo translates the request into the same pipeline as [Chat completions](https://clonvo.com/docs/chat-completions.md), so model ids, [fallbacks](https://clonvo.com/docs/fallbacks.md), [your own keys](https://clonvo.com/docs/byok.md), credits, [plans](https://clonvo.com/docs/plans.md), limits and errors behave the same way, and each request appears in your dashboard’s activity like a chat completion.
> **Note: Stateless**
>
> Clonvo doesn’t store responses. `store` is accepted (it defaults to `true` in the OpenAI SDKs), but nothing is kept, and every response reports `"store": false`. To continue a conversation, send all of it in `input`: the earlier input items followed by the previous response’s `output` items, then your new message or function call outputs. The OpenAI Agents SDK does this by default.
>
> `previous_response_id`, `conversation`, `background` and `prompt` return `400` with the code `unsupported_parameter`. `GET` and `DELETE /v1/responses/{id}`, and the other endpoints under `/v1/responses/`, return `400` with the code `responses_not_stored`.
## Example
```bash
curl https://api.clonvo.com/v1/responses \
-H "Authorization: Bearer $CLONVO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-5-5",
"instructions": "Answer in one sentence.",
"input": "Why is the sky blue?"
}'
```
```python
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.clonvo.com/v1", api_key=os.environ["CLONVO_API_KEY"])
response = client.responses.create(
model="anthropic/claude-sonnet-5-5",
instructions="Answer in one sentence.",
input="Why is the sky blue?",
)
print(response.output_text)
```
```js
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api.clonvo.com/v1", apiKey: process.env.CLONVO_API_KEY });
const response = await client.responses.create({
model: "anthropic/claude-sonnet-5-5",
instructions: "Answer in one sentence.",
input: "Why is the sky blue?",
});
console.log(response.output_text);
```
`output_text` is a convenience the OpenAI SDKs compute from the `message` items in `output`; it isn’t part of the JSON, as with OpenAI.
## Request body
### Required
| Parameter | Type | Description |
| --- | --- | --- |
| `model` | string | Model id, such as `anthropic/claude-sonnet-5-5`. See [Models and model ids](https://clonvo.com/docs/models.md). |
| `input` | string or array | A user message as a string, or an array of [input items](#input-items). |
### Generation
| Parameter | Type | Description |
| --- | --- | --- |
| `instructions` | string | Sent to the model as a system message before `input`. |
| `max_output_tokens` | integer | Upper bound on generated tokens, including reasoning tokens. |
| `temperature` | number | Sampling temperature. Dropped for models that don’t accept it, as on chat completions. |
| `top_p` | number | Nucleus sampling. |
| `text.format` | object | `{"type": "text"}`, `{"type": "json_object"}` or `{"type": "json_schema", "name": …, "schema": {…}, "strict": …}`. See [Structured outputs](#structured-outputs). |
| `text.verbosity` | string | `low`, `medium` or `high`. OpenAI models that accept it. |
| `reasoning.effort` | string | `none`, `minimal`, `low`, `medium`, `high`, `xhigh` or `max`. Each model accepts a subset. See [Reasoning](#reasoning). |
| `reasoning.summary` | string | Accepted and echoed. Reasoning is returned whenever the model returns it. |
### Tools
| Parameter | Type | Description |
| --- | --- | --- |
| `tools` | array | Function tools: `{"type": "function", "name": …, "description": …, "parameters": {…}, "strict": …}`. See [Function calling](#function-calling). |
| `tool_choice` | string or object | `auto`, `none`, `required`, `{"type": "function", "name": …}`, or `{"type": "allowed_tools", "mode": "auto" \| "required", "tools": [{"type": "function", "name": …}]}`. |
| `parallel_tool_calls` | boolean | Allow several function calls in one response. Not sent to Gemini, DeepSeek or Moonshot models. |
`strict` is sent to the provider only when you set it. OpenAI turns strict mode on by default in the Responses API; Clonvo doesn’t, because most providers can’t enforce it. Set `"strict": true` (the OpenAI Agents SDK does) to get it on models that support it.
Built-in tools (`web_search`, `file_search`, `code_interpreter`, `image_generation`, computer use, `mcp`, shell, `apply_patch` and custom tools) return `400` with the code `unsupported_tool`. For web search and page reading, offer Clonvo’s [web tools](https://clonvo.com/docs/tools.md#with-a-models-tool-calling) as function tools and run the calls through `POST /v1/tools/{id}`, or connect the [MCP server](https://clonvo.com/docs/mcp.md).
### Streaming
| Parameter | Type | Description |
| --- | --- | --- |
| `stream` | boolean | Send the response as server-sent events. See [Streaming](#streaming). |
### Routing and billing
| Parameter | Type | Description |
| --- | --- | --- |
| `models` | array of strings | **Extension.** Fallback models, tried in order after `model`. See [Fallbacks and routing](https://clonvo.com/docs/fallbacks.md). |
| `provider` | object | **Extension.** `data_collection`, `order` and `allow_fallbacks`, as on [chat completions](https://clonvo.com/docs/chat-completions.md#routing-and-billing). |
Pass extensions with `extra_body` in the Python SDK, or as extra properties in Node.js.
### Metadata and accepted fields
| Parameter | Description |
| --- | --- |
| `metadata` | String key-value pairs, echoed in the response. Not forwarded or stored. |
| `user` | Your identifier for the end user, passed on as on chat completions. |
| `store` | Accepted; nothing is stored. |
| `include`, `truncation`, `service_tier`, `prompt_cache_key`, `safety_identifier`, `top_logprobs`, `max_tool_calls`, `stream_options` | Accepted and ignored. Encrypted reasoning (`reasoning.encrypted_content`) isn’t returned. |
Other unknown fields are ignored.
## Input items
| Item | Description |
| --- | --- |
| `message` | `role` is `user`, `assistant`, `system` or `developer`; `type: "message"` is optional. `content` is a string or an array of parts. User parts: `input_text`, `input_image` (`image_url` as an https URL or a base64 data URL) and `input_file` (`file_data` as a base64 data URL, or bare base64 with a `filename`). Assistant parts: `output_text`; `refusal` parts are dropped. System and developer messages take text only. |
| `function_call` | A function call from an earlier response: `call_id`, `name`, `arguments`. Consecutive calls, and the assistant message before them, form one assistant turn. |
| `function_call_output` | The result of a call: `call_id` and `output`, a string or an array of `input_text` parts. |
| `reasoning` | Reasoning from an earlier response. Its text (`content`, or `summary` when there is no `content`) is passed back with the assistant turn, which providers such as DeepSeek and Moonshot need in tool loops. `encrypted_content` is ignored. |
Output items you send back keep their `id` and `status`; Clonvo ignores both. `file_id` and `file_url` aren’t supported because Clonvo doesn’t store files. `item_reference` items return `400`, except references to reasoning items (`rs_…`), which are skipped.
## Response
```json
{
"id": "gen-01M67FGKKVQ8ZK2M3N5P7R9S1V",
"object": "response",
"created_at": 1793400000,
"status": "completed",
"background": false,
"completed_at": 1793400002,
"error": null,
"incomplete_details": null,
"instructions": "Answer in one sentence.",
"max_output_tokens": null,
"metadata": {},
"model": "anthropic/claude-sonnet-5-5",
"output": [
{
"id": "msg_01M67FGKKVQ8ZK2M3N5P7R9S1V_0",
"type": "message",
"status": "completed",
"role": "assistant",
"content": [{ "type": "output_text", "text": "Air molecules scatter blue light more than red.", "annotations": [] }]
}
],
"parallel_tool_calls": true,
"previous_response_id": null,
"reasoning": { "effort": null, "summary": null },
"store": false,
"temperature": null,
"text": { "format": { "type": "text" } },
"tool_choice": "auto",
"tools": [],
"top_p": null,
"truncation": "disabled",
"usage": {
"input_tokens": 21,
"input_tokens_details": { "cached_tokens": 0, "cache_write_tokens": 0 },
"output_tokens": 12,
"output_tokens_details": { "reasoning_tokens": 0 },
"total_tokens": 33,
"cost": 0.000243,
"is_byok": false
},
"provider": "Anthropic"
}
```
| Field | Description |
| --- | --- |
| `id` | The generation id (`gen-…`), the same id as in the `x-clonvo-generation-id` header, your dashboard’s activity and [`GET /v1/generation`](https://clonvo.com/docs/other-endpoints.md#get-v1-generation). Clonvo uses it instead of OpenAI’s `resp_…` ids so every response can be looked up. |
| `status` | `completed`; `incomplete` when the model hit `max_output_tokens` or a content filter; `failed` in a [stream that failed](#errors-while-streaming). |
| `incomplete_details.reason` | `max_output_tokens` or `content_filter` when `status` is `incomplete`. |
| `model` | The canonical id of the model that answered. With fallbacks, another model may answer. |
| `output` | `reasoning`, `message` and `function_call` items, in the order the model produced them. Item ids (`rs_…`, `msg_…`, `fc_…`) are derived from the response id. |
| `usage.input_tokens` | Input tokens, including cached and cache-write tokens. |
| `usage.input_tokens_details.cached_tokens` | Input tokens read from the provider’s prompt cache. |
| `usage.input_tokens_details.cache_write_tokens` | Input tokens written to the prompt cache. |
| `usage.output_tokens` | Output tokens, including reasoning tokens. |
| `usage.output_tokens_details.reasoning_tokens` | Tokens spent on reasoning. |
| `usage.cost` | **Extension.** What the request cost in US dollars at list price. See [Credits and billing](https://clonvo.com/docs/credits.md). |
| `usage.is_byok` | **Extension.** `true` when the request ran on your own provider key. |
| `provider` | **Extension.** Display name of the provider that served the request. |
The other fields echo the configuration the request used.
## Function calling
Define functions in `tools`. When the model calls one, the response has a `function_call` item with a `call_id`. Run the function, then send the conversation back with a `function_call_output` for each call:
```python
import json
tools = [{
"type": "function",
"name": "get_weather",
"description": "Current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
"additionalProperties": False,
},
"strict": True,
}]
input = [{"role": "user", "content": "What’s the weather in Paris?"}]
response = client.responses.create(model="google/gemini-3.8-flash", input=input, tools=tools)
input += response.output
for item in response.output:
if item.type == "function_call":
args = json.loads(item.arguments)
input.append({"type": "function_call_output", "call_id": item.call_id, "output": get_weather(**args)})
response = client.responses.create(model="google/gemini-3.8-flash", input=input, tools=tools)
print(response.output_text)
```
```js
const tools = [{
type: "function",
name: "get_weather",
description: "Current weather for a city.",
parameters: { type: "object", properties: { city: { type: "string" } }, required: ["city"], additionalProperties: false },
strict: true,
}];
const input = [{ role: "user", content: "What’s the weather in Paris?" }];
let response = await client.responses.create({ model: "google/gemini-3.8-flash", input, tools });
const calls = response.output.filter((item) => item.type === "function_call");
input.push(...response.output);
for (const call of calls) {
input.push({ type: "function_call_output", call_id: call.call_id, output: await getWeather(JSON.parse(call.arguments)) });
}
response = await client.responses.create({ model: "google/gemini-3.8-flash", input, tools });
console.log(response.output_text);
```
Send the whole `output` back, not just the function calls: reasoning items and any text the model wrote before calling keep the turn intact.
## Structured outputs
```python
response = client.responses.create(
model="openai/gpt-6.1-sol",
input="Extract: Ada Lovelace, born 1815.",
text={"format": {
"type": "json_schema",
"name": "person",
"schema": {
"type": "object",
"properties": {"name": {"type": "string"}, "born": {"type": "integer"}},
"required": ["name", "born"],
"additionalProperties": False,
},
"strict": True,
}},
)
print(response.output_text) # {"name":"Ada Lovelace","born":1815}
```
`client.responses.parse(…)` with a Pydantic model or a Zod schema works too. Models without native JSON Schema support are handled as on chat completions; see [Structured outputs](https://clonvo.com/docs/structured-outputs.md).
## Reasoning
Set `reasoning.effort` to control reasoning; values map to each provider as described in [Reasoning](https://clonvo.com/docs/reasoning.md#effort-values). When the provider returns reasoning text (for example Anthropic, Gemini and DeepSeek), the response starts with a `reasoning` item:
```json
{ "id": "rs_01M67FGKKVQ8ZK2M3N5P7R9S1V_0", "type": "reasoning", "summary": [{ "type": "summary_text", "text": "The user wants…" }] }
```
In tool loops, send reasoning items back with the rest of the output. Anthropic thinking blocks and Gemini thought signatures are restored from the `call_id`, as described in [Multi-turn conversations](https://clonvo.com/docs/reasoning.md#multi-turn-conversations).
OpenAI models are served through OpenAI’s Chat Completions API, which doesn’t return reasoning text or encrypted reasoning, so they produce no `reasoning` items; `usage.output_tokens_details.reasoning_tokens` still reports what they spent.
## Streaming
With `"stream": true`, the response is a stream of [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). Each event has an `event:` line with its type and a `data:` line with the JSON event, which includes `type` and an increasing `sequence_number`. The stream ends after the final `response.*` event; there is no `[DONE]`.
```python
with client.responses.stream(model="anthropic/claude-sonnet-5-5", input="Explain TCP slow start.") as stream:
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
final = stream.get_final_response()
print("\ncost:", final.usage.cost)
```
```js
const stream = client.responses.stream({ model: "anthropic/claude-sonnet-5-5", input: "Explain TCP slow start." });
stream.on("response.output_text.delta", (event) => process.stdout.write(event.delta));
const final = await stream.finalResponse();
console.log("\ncost:", final.usage.cost);
```
```text
event: response.created
data: {"type":"response.created","sequence_number":0,"response":{"id":"gen-01M67FGKKVQ8ZK2M3N5P7R9S1V","object":"response","status":"in_progress","output":[],…}}
event: response.in_progress
data: {"type":"response.in_progress","sequence_number":1,"response":{…}}
event: response.output_item.added
data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"msg_01M67FGKKVQ8ZK2M3N5P7R9S1V_0","type":"message","status":"in_progress","role":"assistant","content":[]}}
event: response.content_part.added
data: {"type":"response.content_part.added","sequence_number":3,"item_id":"msg_01M67FGKKVQ8ZK2M3N5P7R9S1V_0","output_index":0,"content_index":0,"part":{"type":"output_text","text":"","annotations":[]}}
event: response.output_text.delta
data: {"type":"response.output_text.delta","sequence_number":4,"item_id":"msg_01M67FGKKVQ8ZK2M3N5P7R9S1V_0","output_index":0,"content_index":0,"delta":"TCP","logprobs":[]}
event: response.output_text.done
data: {"type":"response.output_text.done","sequence_number":5,"item_id":"msg_01M67FGKKVQ8ZK2M3N5P7R9S1V_0","output_index":0,"content_index":0,"text":"TCP…","logprobs":[]}
event: response.content_part.done
data: {"type":"response.content_part.done","sequence_number":6,…}
event: response.output_item.done
data: {"type":"response.output_item.done","sequence_number":7,"output_index":0,"item":{…,"status":"completed"}}
event: response.completed
data: {"type":"response.completed","sequence_number":8,"response":{…,"status":"completed","usage":{…,"cost":0.000909,"is_byok":false}}}
```
### Events
| Event | When |
| --- | --- |
| `response.created`, `response.in_progress` | Once, with the first output. |
| `response.output_item.added` / `.done` | Around each output item (`reasoning`, `message`, `function_call`). |
| `response.reasoning_summary_part.added` / `.done`, `response.reasoning_summary_text.delta` / `.done` | Reasoning text, as it streams. |
| `response.content_part.added` / `.done` | Around each part of a message (`output_text`, or `refusal`). |
| `response.output_text.delta` / `.done` | Message text. |
| `response.refusal.delta` / `.done` | A refusal, for models that report them separately. |
| `response.function_call_arguments.delta` / `.done` | Function call arguments. |
| `response.completed` | The response finished. `response` has the full `output` and `usage`. |
| `response.incomplete` | The model hit `max_output_tokens` or a content filter. |
| `error`, then `response.failed` | The provider failed after the stream started. |
`response.created` is sent with the first output rather than immediately, so a [fallback](https://clonvo.com/docs/fallbacks.md) before any output reports the model that actually answers. While the stream is quiet, Clonvo sends `: keep-alive` comments, which the SDKs ignore. See [Keep-alive comments](https://clonvo.com/docs/streaming.md#keep-alive-comments).
### Errors while streaming
Errors before the stream starts are normal JSON errors with an HTTP status, as on chat completions. If the provider fails after the stream started, Clonvo sends an `error` event with `code`, `message` and `param`, then `response.failed`, whose `response` has `status: "failed"`, the `error`, the output produced so far and the `usage` you’re charged for. The OpenAI SDKs raise an `APIError` when they read the `error` event.
Close the connection to cancel. Clonvo cancels the provider request and records the generation as cancelled, as described in [Cancelling](https://clonvo.com/docs/streaming.md#cancelling).
## OpenAI Agents SDK
The [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/) calls the Responses API. Point its OpenAI client at Clonvo and pass the model explicitly, so any catalogue model can run an agent:
```python
import os
from openai import AsyncOpenAI
from agents import Agent, OpenAIResponsesModel, Runner, function_tool, set_tracing_disabled
client = AsyncOpenAI(base_url="https://api.clonvo.com/v1", api_key=os.environ["CLONVO_API_KEY"])
set_tracing_disabled(True)
@function_tool
def get_weather(city: str) -> str:
"""Current weather for a city."""
return f"18 °C and sunny in {city}"
agent = Agent(
name="Weather",
instructions="Answer weather questions with the tool.",
tools=[get_weather],
model=OpenAIResponsesModel(model="anthropic/claude-sonnet-5-5", openai_client=client),
)
result = Runner.run_sync(agent, "What’s the weather in Paris?")
print(result.final_output)
```
```js
import OpenAI from "openai";
import { Agent, OpenAIResponsesModel, run, setTracingDisabled, tool } from "@openai/agents";
import { z } from "zod";
const client = new OpenAI({ baseURL: "https://api.clonvo.com/v1", apiKey: process.env.CLONVO_API_KEY });
setTracingDisabled(true);
const getWeather = tool({
name: "get_weather",
description: "Current weather for a city.",
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => `18 °C and sunny in ${city}`,
});
const agent = new Agent({
name: "Weather",
instructions: "Answer weather questions with the tool.",
tools: [getWeather],
model: new OpenAIResponsesModel(client, "anthropic/claude-sonnet-5-5"),
});
const result = await run(agent, "What’s the weather in Paris?");
console.log(result.finalOutput);
```
Turn tracing off (or give the SDK a separate OpenAI key for tracing): by default it sends traces to OpenAI with the client’s key, which is your Clonvo key. Don’t use server-managed conversations (`previous_response_id`, `conversation_id` or OpenAI conversation sessions); the SDK’s default of resending the conversation works. Hosted tools such as `webSearchTool()` aren’t supported.