sone centraldocs
Try the interactive chat

Sample store

Sone assistant

Hello! Ask me about linen shirts, shipping or our size guide.

Uses sample data; does not start a real conversation.

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/responses

Uses 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:

InputError
tools or function-call items such as function_call400 tools_not_supported
background: true400 unsupported_parameter
text.format other than text400 unsupported_parameter
Image or file in a new message400 unsupported_content
Unknown input item type400 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.

EndpointBehavior
GET /responses/{id}Returns the response in the same format.
GET /responses/{id}/input_itemsLists 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}/cancelAlways 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

StatuscodeWhen
400missing_inputResponses request has no input.
400invalid_inputResponses input is not text or an array of items.
400invalid_roleUnknown message role, or a Responses message item without a role.
400missing_user_messageThe final message must be a user message containing text.
400unsupported_itemUnknown Responses input item type.
400conflicting_parametersprevious_response_id and conversation were supplied together. Use one.
400instructions_too_longResponses instructions exceeds 2,000 characters or 4 KB JSON.
400invalid_metadataResponses metadata exceeds 16 keys, 64 characters per key, 512 characters per string value, or 4 KB JSON. Values must be strings.
400safety_identifier_too_longResponses safety_identifier exceeds 64 characters.
400user_too_longResponses user exceeds 64 characters.
400previous_response_id_too_longTrimmed previous_response_id is longer than a response ID. Send the original response id.
400item_id_too_longAn item_reference ID exceeds 64 characters.
400not_cancellableResponses complete within the request; there is no background task to cancel.
400conversation_model_mismatchThe conversation belongs to another assistant. Use the same model or start a new conversation.
404response_not_foundStored response is missing, deleted, older than 30 days or unavailable to the key. On DELETE, also returned when another key stored it.
404previous_response_not_foundThe referenced response is missing, deleted, was created with store: false, or belongs to an assistant unavailable to the key.
404conversation_not_foundNot an API conversation in this workspace, or its assistant is unavailable to the key. Clear the saved ID and start a new conversation.
409conversation_busyThe previous turn is still running. This message was not stored. Wait and retry with the same Idempotency-Key.

All codes: Errors.