Try the interactive chat
Responses API
Use OpenAI Responses, continue with previous_response_id or conversation, and read or delete stored responses.
POST https://pb.sonecentral.com/api/v1/responsesUses OpenAI's Responses format. Continue by sending the previous response ID, without resending the entire history.
When a turn contains several messages, output_text parts and stream deltas separate them with two newlines. This keeps the SDK's output_text or AI SDK's text readable. Each item's sone.text retains the original message.
First message and continuation
const first = await client.responses.create({
model: 'default',
input: 'I would like to book a hair coloring appointment.',
});
console.log(first.output_text);
const second = await client.responses.create({
model: 'default',
previous_response_id: first.id,
input: 'Would Thursday afternoon work?',
});
console.log(second.output_text);output_text is an SDK convenience that joins every output_text part.
Request
Prop
Type
You can use X-Sone-Conversation-Id instead of conversation. Combining conversation and previous_response_id returns 400 conflicting_parameters.
Input system and developer messages are developer context, like instructions. Returned assistant messages and item_reference items help find the conversation: Conversation continuity.
Model settings such as temperature, top_p, max_output_tokens, reasoning, text.verbosity, tool_choice and truncation are ignored. max_output_tokens is echoed in the response but does not change reply length. These inputs are rejected:
| Input | Error |
|---|---|
tools or function-call items such as function_call | 400 tools_not_supported |
background: true | 400 unsupported_parameter |
text.format other than text | 400 unsupported_parameter |
| Image or file in a new message | 400 unsupported_content |
| Unknown input item type | 400 unsupported_item |
reasoning items are silently skipped.
Response
{
"id": "resp_7c1e9a4f2b8d6e3a0f5c9b2d7e4a1c8f",
"object": "response",
"created_at": 1760866200,
"completed_at": 1760866203,
"status": "completed",
"model": "k3m9x2p7q1w8e4r",
"previous_response_id": null,
"conversation": { "id": "conv_x8k2m9p4q7w1r3t" },
"output": [
{
"type": "message",
"id": "msg_w8e4rk3m9x2pq7a",
"status": "completed",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "Of course. Which day works for you?",
"annotations": [],
"logprobs": []
}
],
"sone": {
"id": "msg_w8e4rk3m9x2pq7a",
"role": "assistant",
"type": "text",
"text": "Of course. Which day works for you?",
"created_at": 1760866203
}
}
],
"usage": {
"input_tokens": 10,
"input_tokens_details": { "cached_tokens": 0 },
"output_tokens": 8,
"output_tokens_details": { "reasoning_tokens": 0 },
"total_tokens": 18
},
"sone": { "conversation_id": "conv_x8k2m9p4q7w1r3t", "status": "answered" }
}This example is abbreviated; actual responses also include OpenAI's other defined fields. usage is estimated from text length as described in Chat Completions.
Each assistant message is a separate message output item. Its id is the Sonecentral message ID. Product cards, buttons and files include a plain text fallback in content and structured data in the item's sone field. See Rich messages.
Top-level sone contains the conversation ID and turn outcome. status values match Chat Completions. human and silent turns have empty output. The conversation ID is also returned in X-Sone-Conversation-Id.
Stored responses
Responses are stored by default unless store is false. Stored responses expire after 30 days. The conversation itself remains in Messages.
| Endpoint | Behavior |
|---|---|
GET /responses/{id} | Returns the response in the same format. |
GET /responses/{id}/input_items | Lists this request's own input items, newest first by default; ?order=asc returns oldest first. Single page, always has_more: false. |
DELETE /responses/{id} | Deletes the stored response: { "id": "resp_…", "object": "response.deleted", "deleted": true }. Only the storing key can delete. Does not delete the conversation. |
POST /responses/{id}/cancel | Always returns 400 not_cancellable if the response exists. Responses complete inside the request; no background task exists. |
Only the request's own input items are retained: new visitor messages after the final assistant item, item_reference, and server-key system/developer messages. Publishable-key context is ignored and not stored. Replayed history is not stored again; the conversation retains it.
Storage keeps the newest items, up to 40 items and 8 KB total JSON. The newest item is always retained, truncating its text from the end if needed to fit. Each item's text is limited to 4,000 characters. Items without IDs receive positional IDs such as resp_…_in_0.
Stored request fields are bounded: instructions at 2,000 characters, metadata at 16 keys, safety_identifier and user at 64 characters, and each item_reference ID at 64 characters. instructions and metadata also have a 4 KB JSON limit; multi-byte text can exceed it within the character limit. Oversized requests return 400 and store nothing. previous_response_id is trimmed before storage; longer-than-ID values return 400 previous_response_id_too_long.
Retrying with the same Idempotency-Key, or the same previous_response_id and input, returns the original response with the same ID. It neither generates another reply nor stores another response. This also applies while the conversation belongs to the team.
Any key allowed to call the response's assistant can read the response and input items by ID and continue with previous_response_id. An unavailable assistant's response appears absent: 404 response_not_found, or 404 previous_response_not_found for continuation.
Only the storing key sees developer context, metadata and the submitted end-user identifier. Other keys see instructions, safety_identifier and user as null, empty metadata ({}), and no system or developer input items. Only the storing key can delete; others receive 404 response_not_found. After rotation and revocation, old stored responses expire after 30 days.
Response IDs are hard to guess, but keep them private.
const stored = await client.responses.retrieve('resp_7c1e9a4f2b8d6e3a0f5c9b2d7e4a1c8f');
const items = await client.responses.inputItems.list('resp_7c1e9a4f2b8d6e3a0f5c9b2d7e4a1c8f');
await client.responses.delete('resp_7c1e9a4f2b8d6e3a0f5c9b2d7e4a1c8f');Streaming
With "stream": true, events follow OpenAI's sequence. Each event has a sequence_number starting at 0 and increasing by one. Every assistant message opens and closes its own item:
event: response.created
data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_7c1e","status":"in_progress","conversation":{"id":"conv_x8k2m9p4q7w1r3t"},"output":[]}}
event: response.in_progress
data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_7c1e","status":"in_progress"}}
: sone.activity {"label":"Uygun saatlere bakılıyor"}
event: response.output_item.added
data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"type":"message","id":"msg_w8e4","status":"in_progress","role":"assistant","content":[],"sone":{…}}}
event: response.content_part.added
data: {"type":"response.content_part.added","sequence_number":3,"item_id":"msg_w8e4","output_index":0,"content_index":0,"part":{"type":"output_text","text":"","annotations":[],"logprobs":[]}}
event: response.output_text.delta
data: {"type":"response.output_text.delta","sequence_number":4,"item_id":"msg_w8e4","output_index":0,"content_index":0,"delta":"Of course. Which day works ","logprobs":[]}
event: response.output_text.delta
data: {"type":"response.output_text.delta","sequence_number":5,"item_id":"msg_w8e4","output_index":0,"content_index":0,"delta":"for you?","logprobs":[]}
event: response.output_text.done
data: {"type":"response.output_text.done","sequence_number":6,"item_id":"msg_w8e4","output_index":0,"content_index":0,"text":"Of course. Which day works for you?","logprobs":[]}
event: response.content_part.done
data: {"type":"response.content_part.done","sequence_number":7,"item_id":"msg_w8e4","output_index":0,"content_index":0,"part":{"type":"output_text","text":"Of course. Which day works for you?","annotations":[],"logprobs":[]}}
event: response.output_item.done
data: {"type":"response.output_item.done","sequence_number":8,"output_index":0,"item":{"type":"message","id":"msg_w8e4","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Of course. Which day works for you?","annotations":[],"logprobs":[]}],"sone":{…}}}
event: response.completed
data: {"type":"response.completed","sequence_number":9,"response":{"id":"resp_7c1e","status":"completed","sone":{"conversation_id":"conv_x8k2m9p4q7w1r3t","status":"answered"}}}Objects are abbreviated here; actual events contain all fields.
: sone.activity is an SSE comment, not an event. Its label is a short Turkish activity description, such as “Uygun saatlere bakılıyor” (Checking available times), and it has no sequence_number. OpenAI SDKs skip comments. To display activity, parse lines beginning with : sone.activity as JSON after the prefix. : ping lines are comments too.
import OpenAI from 'openai';
const stream = await client.responses.create({
model: 'default',
input: 'I would like to book a hair coloring appointment.',
stream: true,
});
try {
for await (const event of stream) {
if (event.type === 'response.output_text.delta') process.stdout.write(event.delta);
if (event.type === 'response.output_item.done') process.stdout.write('\n');
}
} catch (error) {
// Post-start failure: the SDK throws APIError for the error event.
if (error instanceof OpenAI.APIError) console.error(error.code, error.message);
else throw error;
}After streaming begins, failures send an error event carrying code and message, then response.failed, and close the stream. Responses never sends data: [DONE]; it ends with response.completed or response.failed. OpenAI's Node SDK throws on error; Python exposes an event with type: "error". See Errors.
Errors
| Status | code | When |
|---|---|---|
400 | missing_input | Responses request has no input. |
400 | invalid_input | Responses input is not text or an array of items. |
400 | invalid_role | Unknown message role, or a Responses message item without a role. |
400 | missing_user_message | The final message must be a user message containing text. |
400 | unsupported_item | Unknown Responses input item type. |
400 | conflicting_parameters | previous_response_id and conversation were supplied together. Use one. |
400 | instructions_too_long | Responses instructions exceeds 2,000 characters or 4 KB JSON. |
400 | invalid_metadata | Responses metadata exceeds 16 keys, 64 characters per key, 512 characters per string value, or 4 KB JSON. Values must be strings. |
400 | safety_identifier_too_long | Responses safety_identifier exceeds 64 characters. |
400 | user_too_long | Responses user exceeds 64 characters. |
400 | previous_response_id_too_long | Trimmed previous_response_id is longer than a response ID. Send the original response id. |
400 | item_id_too_long | An item_reference ID exceeds 64 characters. |
400 | not_cancellable | Responses complete within the request; there is no background task to cancel. |
400 | conversation_model_mismatch | The conversation belongs to another assistant. Use the same model or start a new conversation. |
404 | response_not_found | Stored response is missing, deleted, older than 30 days or unavailable to the key. On DELETE, also returned when another key stored it. |
404 | previous_response_not_found | The referenced response is missing, deleted, was created with store: false, or belongs to an assistant unavailable to the key. |
404 | conversation_not_found | Not an API conversation in this workspace, or its assistant is unavailable to the key. Clear the saved ID and start a new conversation. |
409 | conversation_busy | The previous turn is still running. This message was not stored. Wait and retry with the same Idempotency-Key. |
All codes: Errors.