Build
Updated
Create a response
POST /v1/responses creates one turn of a conversation. This page covers the request fields a turn needs, how input becomes a turn, and the turns that follow.
POST /v1/responses creates a response, which is one turn of a
conversation. To continue that conversation, send the next turn with
previous_response_id set to the id you got back.
A first turn
Section titled “A first turn”curl https://api.helping.io/v1/responses \ -H "Authorization: Bearer $HELPINGIO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-sonnet-5", "input": "Say hello in exactly 3 words." }'import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api.helping.io/v1", apiKey: process.env.HELPINGIO_API_KEY,});
const response = await client.responses.create({ model: "anthropic/claude-sonnet-5", input: "Say hello in exactly 3 words.",});
console.log(response.output_text);import os
from openai import OpenAI
client = OpenAI( base_url="https://api.helping.io/v1", api_key=os.environ["HELPINGIO_API_KEY"],)
response = client.responses.create( model="anthropic/claude-sonnet-5", input="Say hello in exactly 3 words.",)
print(response.output_text)The reply is a response object. The fields you will read most are id,
status, output, and usage:
{ "background": false, "completed_at": 1767225600, "conversation": null, "created_at": 1767225600, "error": null, "frequency_penalty": 0.0, "id": "resp_ZXhhbXBsZS9yZXNwL2ZpcnN0LXR1cm4AAAAAAAAAAAAAAAAAAAAAAA", "incomplete_details": null, "instructions": null, "max_output_tokens": null, "max_tool_calls": null, "metadata": null, "model": "anthropic/claude-sonnet-5", "object": "response", "output": [ { "content": [ { "annotations": [], "logprobs": [], "text": "Hello there, friend.", "type": "output_text" } ], "id": "msg_00000000000000000000000000000001", "role": "assistant", "status": "completed", "type": "message" } ], "parallel_tool_calls": true, "presence_penalty": 0.0, "previous_response_id": null, "prompt_cache_key": null, "reasoning": null, "safety_identifier": null, "service_tier": "default", "status": "completed", "store": true, "temperature": 1.0, "text": { "format": { "type": "text" } }, "tool_choice": "auto", "tools": [], "top_logprobs": 0, "top_p": 1.0, "truncation": "disabled", "usage": { "input_tokens": 100, "input_tokens_details": { "cache_write_tokens": 0, "cached_tokens": 0 }, "output_tokens": 20, "output_tokens_details": { "reasoning_tokens": 0 }, "total_tokens": 120 }}The full object also echoes the sampling and tool fields you sent, or their
defaults. Fields are snake_case throughout.
Continuing a conversation
Section titled “Continuing a conversation”Send the next user turn with previous_response_id. The API keeps the
conversation history, so you send only the new turn, never the history.
{ "input": "And again.", "previous_response_id": "resp_ZXhhbXBsZS9yZXNwL2ZpcnN0LXR1cm4AAAAAAAAAAAAAAAAAAAAAAA"}Two rules apply to the turns after the first:
- A conversation is bound to the key that started it. A next turn sent
with a different API key answers the same
404as an unknown id, even inside the same organization. A rotated key keeps its conversations; revoking a key ends them for everyone. modelcarries forward. Omit it on a later turn and the conversation’s model is used. Supply it to switch models for that turn. See Choose a model.
How input becomes a turn
Section titled “How input becomes a turn”input carries exactly one thing: the current user turn.
| You send | What happens |
|---|---|
| A bare string | One user message |
message items with role user |
Their text joins the user message. Several user items concatenate in wire order, separated by a blank line |
A message item with role assistant |
400. Continue the conversation with previous_response_id instead |
A message item with role system or developer |
400. Use instructions for per-request steering |
A function_call_output item |
The tool-result path. See Function calling |
A function_call item with its matching function_call_output |
Carried onto the new conversation as a real tool exchange. The call’s arguments and the result’s text become context; the tool’s name is not sent |
A function_call with no matching output, or an output with no call |
400 naming which item and which half is missing |
A reasoning item |
Stored and readable, never sent to the model |
| An item-reference item | 400. A first turn does not resolve item references, so send the item itself |
Multi-part user content keeps its text parts (input_text, output_text,
text), joined with newlines. Every other part fails loudly rather than
vanishing: input_image, input_file, input_video, summary_text,
reasoning_text and refusal parts are each a 400 naming the part. Only
text reaches the model, so send what it needs as an input_text part: a
file’s text, or a description of an image.
A user item whose content is empty is skipped. An input array that yields no
user text at all is a 400. When that input holds a function_call_output,
the message says so instead, because a new conversation has no tool call
waiting for a result. Every refusal in this section sets param to input, and
a refusal about one item names its index and type in the message.
Three more refusals apply to the whole request body:
- A string anywhere in the body that contains a NUL character (U+0000) is a
400naming where it is. Remove or replace the character before sending. conversationandprevious_response_idtogether are a400: each names the conversation a response continues, so send one of them.- A malformed
previous_response_idis a400, and one that names no response your key can see is a404.
Request fields
Section titled “Request fields”The fields a turn needs, and what each one does. Three things are
true of this surface that are worth knowing before you read the tables:
unknown fields are ignored rather than rejected, so a newer SDK never fails
here; a field that parses but is not applied is listed as accepted,
ignored, so nothing is silently half-supported; and the response object
echoes back model, previous_response_id, metadata, tools,
tool_choice, truncation, instructions, and the effective
max_output_tokens. All of them survive a retrieve, with one difference worth
knowing when you answer a tool call, described under
what a retrieve reports.
The request-only fields it does not echo report the specification’s default
value regardless of what you sent, except the generation settings, which report
the value the response was generated with.
Required
Section titled “Required”| Field | Notes |
|---|---|
model |
Required on a first turn. Carries forward on later turns |
input |
Required on a new turn. String or item array, as above |
Conversation
Section titled “Conversation”| Field | Notes |
|---|---|
previous_response_id |
Continue the conversation that produced this response |
instructions |
Per-request steering, echoed, never inherited |
metadata |
Stored with the response and returned when you retrieve it. Up to 16 pairs of key and value, keys up to 64 characters, values strings up to 512 characters |
safety_identifier |
An opaque id for your end user, up to 64 characters. Sent where the model’s provider has a per-end-user field, as a keyed hash of your value, never the value itself, so one end user can be restricted without restricting your key. Echoed as you sent it |
user |
Deprecated in favor of safety_identifier. Used as your end user’s id when safety_identifier is absent, and ignored when both are sent. It does not group requests for caching, and the response does not echo its value |
| Field | Notes |
|---|---|
tools |
Your function tools. Echoed. See Function calling |
tool_choice |
none, auto, required, {"type": "function", "name": …}, or an allowed_tools object. Echoed. A function choice needs a name; allowed_tools takes mode auto or required and a name on each entry; required needs at least one tool; and naming a tool that is not in your tools is a 400 naming the tool |
Output
Section titled “Output”| Field | Notes |
|---|---|
stream |
true for server-sent events. Defaults to false. See Streaming |
stream_options |
include_obfuscation, on by default, pads each streamed delta event so its size does not track the length of the text in it. Set it to false to turn the padding off |
max_output_tokens |
Minimum 16. Echoed as the effective value, which can be lower than you asked for, and carried forward to a later turn that omits it |
truncation |
disabled (the default) refuses a turn that does not fit the model’s context window, with the code context_length_exceeded; auto drops the oldest turns so it fits. See How truncation differs |
Everything else
Section titled “Everything else”Generation settings, the fields that are accepted but advisory, and the fields that are refused are listed on Compatibility and versioning. The API reference carries every field of the request body.
Headers
Section titled “Headers”| Header | Notes |
|---|---|
Authorization: Bearer sk_… |
Required. See API keys |
Content-Type: application/json |
Recommended on POST. The body is read as JSON whatever this header says |
Idempotency-Key |
Optional, and the official SDKs do not send it on their own: pass it in the request options. Visible ASCII, at most 255 bytes. A retry with the same key and body returns the first response, so the model produces output and bills once; the same key with a different body is a 409. Remembered for 24 hours. See Idempotency |
Waiting for the result
Section titled “Waiting for the result”A non-streaming request waits up to 90 seconds. A
response that is not finished by then comes back in progress: the same id,
a status of queued or in_progress, an empty output, and HTTP 200. It
is not an error, and it is not the end of the work. Poll
GET /v1/responses/{response_id} for the result.
{ "background": false, "completed_at": null, "conversation": null, "created_at": 1767225600, "error": null, "frequency_penalty": 0.0, "id": "resp_ZXhhbXBsZS9yZXNwL2luLXByb2dyZXNzLWF0LXRoZS13YWl0LWJvdQ", "incomplete_details": null, "instructions": null, "max_output_tokens": 1024, "max_tool_calls": null, "metadata": null, "model": "anthropic/claude-sonnet-5", "object": "response", "output": [], "parallel_tool_calls": true, "presence_penalty": 0.0, "previous_response_id": null, "prompt_cache_key": null, "reasoning": null, "safety_identifier": null, "service_tier": "default", "status": "in_progress", "store": true, "temperature": 1.0, "text": { "format": { "type": "text" } }, "tool_choice": "auto", "tools": [], "top_logprobs": 0, "top_p": 1.0, "truncation": "disabled"}For work that takes longer than a few seconds, or when you want tokens as they arrive, use streaming, which is bounded at 300 seconds instead.