# Responses API

Page: https://docs.sonecentral.com/en/responses
Markdown: https://docs.sonecentral.com/en/responses.md

> Use OpenAI Responses, continue with previous_response_id or conversation, and read or delete stored responses.

```http
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

**OpenAI JS**

```ts
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);
```

**OpenAI Python**

```python
first = client.responses.create(
    model="default",
    input="I would like to book a hair coloring appointment.",
)
print(first.output_text)

second = client.responses.create(
    model="default",
    previous_response_id=first.id,
    input="Would Thursday afternoon work?",
)
print(second.output_text)
```

**curl**

```bash
curl https://pb.sonecentral.com/api/v1/responses \
  -H "Authorization: Bearer $SONE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "model": "default", "input": "I would like to book a hair coloring appointment." }'
```

`output_text` is an SDK convenience that joins every `output_text` part.

## Request

| Field | Type | Description |
| --- | --- | --- |
| `model` | string | Assistant ID or "default". Required. |
| `input` | string \| array | New message as text or OpenAI input items: message items with roles and item_reference items. input_text and output_text parts are read. Each item_reference ID is limited to 64 characters. The final item must be a visitor text message. Required. |
| `previous_response_id` | string | Previous response ID: resp_ followed by 32 characters. Continues the same conversation. Cannot be combined with conversation. Longer values are rejected. |
| `conversation` | string \| { id: string } | Conversation ID, sone.conversation_id. An alternative to previous_response_id. |
| `store` | boolean | false disables response storage: GET /responses/{id} and previous_response_id cannot find it. Default: true. |
| `instructions` | string | Server keys only: informational developer context, at most 2,000 characters and 4 KB JSON. Longer values are rejected. Ignored, not stored and omitted from responses for publishable keys. On stored reads, visible only to the storing key. |
| `stream` | boolean | Stream the response as events. Default: false. |
| `user` | string | Server keys only: internal visitor identifier for conversation lookup, at most 64 characters. Longer values are rejected. Not returned or stored. |
| `safety_identifier` | string | Same lookup as user, taking precedence when both are supplied. At most 64 characters. Returned and stored for server keys; ignored, not stored and omitted for publishable keys. |
| `metadata` | object | Echoed unchanged; not used for conversation lookup. Stored reads expose it only to the storing key. Up to 16 keys, each key 64 characters, each string value 512 characters, and 4 KB JSON total. |

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](https://docs.sonecentral.com/en/kimlik-dogrulama.md#gelistirici-baglami), like `instructions`. Returned assistant messages and `item_reference` items help find the conversation: [Conversation continuity](https://docs.sonecentral.com/en/sohbet-surekliligi.md#sohbet-nasil-bulunur).

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

```json
{
  "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](https://docs.sonecentral.com/en/chat-completions.md).

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](https://docs.sonecentral.com/en/zengin-mesajlar.md).

Top-level `sone` contains the conversation ID and turn outcome. `status` values match [Chat Completions](https://docs.sonecentral.com/en/chat-completions.md). `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.

```ts
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:

```txt
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.

**OpenAI JS**

```ts
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;
}
```

**OpenAI Python**

```python
stream = client.responses.create(
    model="default",
    input="I would like to book a hair coloring appointment.",
    stream=True,
)

for event in stream:
    if event.type == "response.output_text.delta":
        print(event.delta, end="", flush=True)
    elif event.type == "response.output_item.done":
        print()
    elif event.type == "error":
        print(event.code, event.message)
```

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](https://docs.sonecentral.com/en/hatalar.md#akis-sirasinda-hata).

## 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](https://docs.sonecentral.com/en/hatalar.md).
