Core API
Responses API
Build OpenAI-compatible Responses requests for new clients and agent workflows.
On this page
This page is for developers building a new OpenAI-compatible client or an agent workflow such as Codex CLI.
Send POST https://api.clssai.com/v1/responses with a JSON body. The endpoint has been verified with both non-streaming and streaming requests.
Minimal request#
curl https://api.clssai.com/v1/responses \
-H "Authorization: Bearer $CLSSAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-nano",
"input": "Reply with exactly: Responses works."
}'Read the response#
Assistant output is represented as typed items in output. For a text response, find the message item and read its content[0].text. When the upstream response supplies usage.cost, that value is the charge authority.
{
"id": "resp_example",
"object": "response",
"model": "openai/gpt-5.4-nano",
"output": [{
"type": "message",
"role": "assistant",
"content": [{"type": "output_text", "text": "Responses works."}]
}],
"usage": {
"input_tokens": 12,
"output_tokens": 5,
"total_tokens": 17,
"cost": 0.00001
}
}Do not assume that the first output item is always a message or that the first content item is always text. Agent and tool workflows can return other typed items.
Python#
Install the official client with pip install openai, then run this on your server.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.clssai.com/v1",
api_key=os.environ["CLSSAI_API_KEY"],
)
response = client.responses.create(
model="openai/gpt-5.4-nano",
input="Reply with exactly: Responses works.",
)
print(response.output[0].content[0].text)
print(response.usage)JavaScript#
Install the official client with npm install openai. This example runs in Node.js.
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.clssai.com/v1",
apiKey: process.env.CLSSAI_API_KEY,
});
const response = await client.responses.create({
model: "openai/gpt-5.4-nano",
input: "Reply with exactly: Responses works.",
});
console.log(response.output[0].content[0].text);
console.log(response.usage);Streaming#
Set stream: true to receive server-sent events. Every frame arrives as a data: line whose JSON carries a type field; the gateway does not send a named SSE event: field, so dispatch on type. Append the delta from each response.output_text.delta frame, read the final response object from response.completed, and stop when the stream sends data: [DONE].
curl -N https://api.clssai.com/v1/responses \
-H "Authorization: Bearer $CLSSAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-nano",
"input": "Count from one to three.",
"stream": true
}'data: {"type":"response.created","response":{"id":"resp_example","status":"in_progress"},"sequence_number":0}
data: {"type":"response.output_item.added","output_index":0,"item":{"type":"message","role":"assistant","content":[]},"sequence_number":2}
data: {"type":"response.output_text.delta","output_index":0,"content_index":0,"delta":"One","sequence_number":4}
data: {"type":"response.output_text.delta","output_index":0,"content_index":0,"delta":", two, three.","sequence_number":5}
data: {"type":"response.completed","response":{"id":"resp_example","status":"completed"},"sequence_number":16}
data: [DONE]A full turn uses this order: response.created → response.in_progress → response.output_item.added → response.content_part.added → response.output_text.delta (one or more frames) → response.output_text.done → response.content_part.done → response.output_item.done → response.completed → data: [DONE]. Ignore frame types you do not handle rather than treating them as errors.
Stream with Python#
This raw HTTP example uses requests so the data: framing and [DONE] marker remain visible.
import json
import os
import requests
with requests.post(
"https://api.clssai.com/v1/responses",
headers={
"Authorization": f"Bearer {os.environ['CLSSAI_API_KEY']}",
"Content-Type": "application/json",
},
json={
"model": "openai/gpt-5.4-nano",
"input": "Count from one to three.",
"stream": True,
},
stream=True,
timeout=120,
) as response:
response.raise_for_status()
final_response = None
for raw_line in response.iter_lines():
line = raw_line.decode("utf-8")
if not line.startswith("data:"):
continue
data = line.removeprefix("data:").strip()
if data == "[DONE]":
break
event = json.loads(data)
if event.get("type") == "response.output_text.delta":
print(event["delta"], end="", flush=True)
elif event.get("type") == "response.completed":
final_response = event["response"]
print()
print(final_response)Stream with JavaScript#
This Node.js example buffers partial network chunks, parses each data: line, dispatches on the JSON type, and exits on [DONE].
const response = await fetch("https://api.clssai.com/v1/responses", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CLSSAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "openai/gpt-5.4-nano",
input: "Count from one to three.",
stream: true,
}),
});
if (!response.ok) throw new Error(await response.text());
const decoder = new TextDecoder();
let buffer = "";
let finalResponse = null;
stream: for await (const chunk of response.body) {
buffer += decoder.decode(chunk, { stream: true });
const lines = buffer.split(/\r?\n/);
buffer = lines.pop() ?? "";
for (const line of lines) {
if (!line.startsWith("data:")) continue;
const data = line.slice("data:".length).trim();
if (data === "[DONE]") break stream;
const event = JSON.parse(data);
if (event.type === "response.output_text.delta") {
process.stdout.write(event.delta);
} else if (event.type === "response.completed") {
finalResponse = event.response;
}
}
}
process.stdout.write("\n");
console.log(finalResponse);Choose Responses or Chat Completions#
Use Responses for a new client or a Codex-style agent integration. Its typed output and event model are designed for evolving agent workflows. Use Chat Completions when you already have an OpenAI chat integration built around messages and choices.
Common mistakes#
- Include
/v1in the OpenAI SDK base URL. - Keep the API key and SDK on your server; a browser call exposes the key to every visitor.
- The gateway injects
userwhen you omit it, and the Responses API can echo that value asuser. Pass your ownuserto override it. previous_response_idandstore: trueare rejected with400 invalid_prompt; the gateway requiresprevious_response_id: nullandstore: false. There is no server-side conversation persistence — resend the conversation ininputand keep state in your application.- Production clients should use a complete
author/modelID. Bare names are resolved as described on Models & pricing, while:freeand:batchvariants must be written in full. - Do not subscribe to named SSE events on
/v1/responses; frames carry noevent:field. Parse eachdata:payload'stype, and stop atdata: [DONE]. - Parse typed
outputitems and streaming frames instead of assuming every result uses one fixed array position. - Retain
cf-raywhen asking support to trace an inference request.
Find answers to common questions or diagnose a failed request.
Frequently asked questions →Troubleshoot errors →