Responses API
Build multi-step agents that keep reasoning intact across turns. The Responses API is the only endpoint that carries the model's chain of thought between calls, which keeps tool loops accurate and performant. It also powers search grounding, file inputs, and background execution.
Choose how context travels. Use stateless encrypted reasoning replay when you want full control and no server state. Use previous_response_id when you want the server to manage history. For single-shot or straightforward turns that plug into existing OpenAI code without reasoning continuity, Chat Completions is simpler.
The Responses API serves Muse Spark (text) and Muse Image for image generation. For which models run on which endpoint, see model availability.
How it works
With chat completion, you manage history client-side: you resend the full messages array each turn and the payload grows as the conversation grows.
The Responses API takes an input as a string or an array of typed items and offers two context modes:
- Stateless encrypted replay (recommended): resend the conversation each turn. Reasoning travels as encrypted reasoning items. Nothing is stored server-side.
- Server-managed history: the server stores each response and you continue from
previous_response_id. Send only new input each turn.
Both modes preserve chain of thought across turns. A plain messages array cannot.
When you build the input array yourself, it carries typed items instead of plain {role, content} messages:
- Messages with a
role(developer,system,user, orassistant) and typed content blocks such asinput_text,input_image,input_file,input_video, andoutput_textfor replayed assistant turns. See Input content types. reasoningitems that replay the model's chain of thought across turns. See Reasoning items in multi-turn input.
Use the Responses API when you want reasoning continuity across turns for tool loops, cleaner multi-turn code, server-managed context, or Model API features that run through this endpoint like search grounding and file references.
Basic usage
Call POST /v1/responses with a model and input.
pythonimport osfrom openai import OpenAIclient = OpenAI(base_url="https://api.meta.ai/v1",api_key=os.environ["MODEL_API_KEY"],)response = client.responses.create(model="muse-spark-1.3",input="What is the capital of France?",)print(response.model_dump_json(indent=2))
typescriptimport OpenAI from 'openai';const apiKey = process.env.MODEL_API_KEY;if (!apiKey) {throw new Error('MODEL_API_KEY is not set');}const client = new OpenAI({baseURL: 'https://api.meta.ai/v1',apiKey,});const response = await client.responses.create({model: 'muse-spark-1.3',input: 'What is the capital of France?',});console.log(JSON.stringify(response, null, 2));
pythonimport jsonimport osimport requestsresponse = requests.post("https://api.meta.ai/v1/responses",headers={"Authorization": f"Bearer {os.environ['MODEL_API_KEY']}","Content-Type": "application/json",},json={"model": "muse-spark-1.3","input": "What is the capital of France?",},)response.raise_for_status()print(json.dumps(response.json(), indent=2))
shellcurl -X POST "https://api.meta.ai/v1/responses" \-H "Authorization: Bearer $MODEL_API_KEY" \-H "Content-Type: application/json" \-d '{"model": "muse-spark-1.3","input": "What is the capital of France?"}'
Example response
json{"id": "resp_abc123","object": "response","created_at": 1714502400,"status": "completed","model": "muse-spark-1.3","store": true,"temperature": 1.0,"top_p": 1.0,"max_output_tokens": null,"tools": [],"tool_choice": "auto","parallel_tool_calls": true,"service_tier": "auto","output": [{"id": "msg_abc123:456","type": "message","role": "assistant","status": "completed","content": [{"type": "output_text","text": "The capital of France is **Paris**.","annotations": []}]}],"usage": {"input_tokens": 69,"output_tokens": 163,"total_tokens": 232}}
The response returns an id. Pass that id as previous_response_id to continue the conversation.
Multi-turn conversations
Chain turns with previous_response_id. The server appends your new input to stored history, so you send only what changed.
Python (OpenAI SDK)import osfrom openai import OpenAIclient = OpenAI(base_url="https://api.meta.ai/v1",api_key=os.environ["MODEL_API_KEY"],)# First turnresponse = client.responses.create(model="muse-spark-1.3",input="What is the capital of France?",)print(response.output_text)# Second turn -- continues the conversationresponse = client.responses.create(model="muse-spark-1.3",input="What is the population of that city?",previous_response_id=response.id,)print(response.output_text)# Third turn -- the server still has the full contextresponse = client.responses.create(model="muse-spark-1.3",input="How does that compare to London?",previous_response_id=response.id,)print(response.output_text)
Each call sends only the new user input. The server reconstructs the full conversation from the chain of response IDs.
Setting instructions
Use instructions to set tone, persona, and output rules for the current request. It is developer-level: the server inserts it at the top of the model's context, it takes priority over input, and it is equivalent to sending a developer-role message. Use whichever fits your code, you do not need both. instructions applies to the current request only and is not carried across previous_response_id, so resend it on each turn where you want it applied.
Input content types
When you manage history yourself by building the input array instead of chaining with previous_response_id, the array is flexible. It accepts multiple system messages, a developer-role message (recommended for behavior instructions; system-role content is applied at the same level), and multiple content blocks within a single message. User blocks include input_text, input_image, input_file, and input_video (see video understanding). Replay prior assistant turns with output_text so the model sees its earlier responses accurately. Assistant messages also accept an optional phase field, see Message phase.
The example below builds a short multi-turn conversation as an input array:
json{"model": "muse-spark-1.3","input": [{"role": "developer", "content": [{"type": "input_text", "text": "You are a helpful geography assistant."}]},{"role": "user", "content": [{"type": "input_text", "text": "What is the capital of France?"}]},{"role": "assistant", "content": [{"type": "output_text", "text": "The capital of France is Paris."}]},{"role": "user", "content": [{"type": "input_text", "text": "What is its population?"}]}]}
Reasoning items in multi-turn input
Muse Spark reasons internally, see reasoning for controls. To preserve that reasoning across turns, return reasoning items from a response's output as part of the next input. You can mix reasoning items with other types such as messages and function_call_output when you construct multi-turn input.
Reasoning replay is encrypted-only: encrypted_content is omitted by default. To receive it, set include: ["reasoning.encrypted_content"] on the request. This works whether or not the response is stored. You can request encrypted content with store: true or store: false. For stateless replay, pair it with store: false so the server keeps no copy of the conversation:
json{"model": "muse-spark-1.3","input": "...","store": false,"include": ["reasoning.encrypted_content"]}
Then pass the reasoning item back on the next turn with its encrypted_content. Every reasoning input item must include a summary field (send summary: [] when you have no summary). The id field (an rs_-prefixed ID returned in the prior response) is optional on input. Include it when available, but clients doing stateless replay can omit it since encrypted_content carries the full state:
json{"type": "reasoning","id": "rs_abc123","summary": [],"encrypted_content": "..."}
Replay reasoning items in multi-step or agentic workflows. Without replay, the model loses chain-of-thought context across turns. For multi-turn and agentic use, stateless encrypted replay (store: false with include: ["reasoning.encrypted_content"]) is the recommended path. It keeps no state on the server and works across OpenAI-compatible tooling.
Compacting conversations with reasoning
Long agent loops can approach the context window. Model API does not trim for you: truncation: "auto" is rejected (HTTP 400, only the default "disabled" is accepted) and an over-context request fails with HTTP 400. You compact history yourself, and encrypted reasoning items shape how you do it.
Encrypted reasoning items are opaque: you cannot summarize or rewrite one, only keep it whole or drop it. Compact at the level of whole turns.
- Keep recent reasoning, drop old: retain reasoning items for turns still in play (the current tool loop) and drop reasoning from older, settled turns. The server does not require a reasoning item to travel with its message or tool call, so dropping one is allowed. You lose that turn's chain of thought.
- Replay what you keep, intact: every retained item must carry its
encrypted_content. Replaying a barers_id without it returnsHTTP 400("Referenced reasoning item ... was not found or has expired"). Ids must be unique and you must preservefunction_call/function_call_outputpairing and valid conversation structure. - Measure before you send: call
POST /v1/responses/input_tokenswith your trimmed input to confirm it fits before generating.
If you prefer not to manage this, use store: true with previous_response_id and let the server reconstruct history (see Multi-turn conversations). The trade-off is server-side state instead of stateless replay.
Message phase
Assistant messages carry an optional phase field that labels what the content is:
phase: "commentary": intermediate assistant content, such as a preamble the model emits before calling a tool.phase: "final_answer": the model's final answer content. This value is accepted on input only. The model does not stamp it on responses.
When the model emits intermediate commentary before a tool call, it tags those messages with phase: "commentary". Final-answer messages return with no phase field. The model does not emit phase: "final_answer", so do not expect it in responses. When the model returns a phase on an assistant message, preserve and resend it as you replay turns by building the input array yourself. Dropping it can degrade quality. If a message has no phase, there is nothing to preserve. Treat it as a final answer. phase is not used on user messages.
phase also matters for conversation structure. Intermediate assistant text that precedes a function_call must be tagged phase: "commentary". Replaying that text as an ordinary final answer immediately before a function_call returns HTTP 400. The server does not silently reinterpret a final answer as commentary.
json{"type": "message","role": "assistant","phase": "commentary","content": [{"type": "output_text", "text": "Let me check the weather first."}]}
Conversation structure
When you build input arrays manually, the server validates structure and rejects sequences it cannot interpret safely with HTTP 400 instead of guessing. Keep these rules in mind:
- Pair
function_callwithfunction_call_output: when a request containsfunction_callitems and noprevious_response_id, everyfunction_call_output.call_idmust match afunction_call.call_idin the same request. A mismatch returnsHTTP 400(param: "call_id"). Everycall_idmust also be 1–64 characters, an empty or overlength value returnsHTTP 400(param: "call_id"). - Order reasoning replay correctly: a replayed
reasoningitem must be followed by an assistant message or afunction_callbefore any newuser,system, ordevelopermessage, otherwise the request returnsHTTP 400(param: "input"). - Tag intermediate assistant text as commentary: see Message phase.
When you pass previous_response_id, the server reconstructs ordering from stored history, so these checks are relaxed and you send only new input. See error handling for error shapes.
Retrieving a response
Responses are stored when store: true (the default). Fetch a stored response by its id:
pythonimport osfrom openai import OpenAIclient = OpenAI(base_url="https://api.meta.ai/v1",api_key=os.environ["MODEL_API_KEY"],)response = client.responses.retrieve("resp_abc123")print(response.model_dump_json(indent=2))
pythonimport jsonimport osimport requestsresponse = requests.get("https://api.meta.ai/v1/responses/resp_abc123",headers={"Authorization": f"Bearer {os.environ['MODEL_API_KEY']}"},)response.raise_for_status()print(json.dumps(response.json(), indent=2))
shellcurl -X GET "https://api.meta.ai/v1/responses/resp_abc123" \-H "Authorization: Bearer $MODEL_API_KEY"
A retrieved response carries the same self-describing fields as the original POST. It echoes request-derived parameters (store, tools, tool_choice, text, temperature, top_p, max_output_tokens, service_tier, background, instructions, previous_response_id, truncation, metadata, and parallel_tool_calls), plus incomplete_details, completed_at, created_at, and the full usage breakdown (output_tokens_details.reasoning_tokens and input_tokens_details.cached_tokens). Storing a response and reading it back later to inspect a background response or audit an earlier turn returns those fields intact instead of null or zeroed. completed_at is the exception: it is set only once the response reaches status: "completed" and is null for responses that haven't completed (such as in_progress, failed, or cancelled).
output_tokens_details.reasoning_tokens counts chain-of-thought tokens. User-visible commentary such as a preamble before a tool call (see Message phase) counts toward output_tokens but is not included in reasoning_tokens.
Streaming retrieval for background responses
For background responses, retrieve with stream=true to receive an SSE stream instead of a JSON body:
curlcurl "https://api.meta.ai/v1/responses/resp_abc123?stream=true" \-H "Authorization: Bearer $MODEL_API_KEY"
The stream delivers lifecycle events as the background response progresses:
response.created: emitted immediately with the response identity.- A terminal event (
response.completed,response.failed, orresponse.incomplete): emitted once the response reaches a terminal state, carrying the full final response. [DONE]: signals the end of the stream.
If the response is still in progress, the stream live-tails until it reaches a terminal state. Use starting_after to resume from a sequence number. Events with sequence_number at or below it are skipped.
Streaming
Set stream=True to receive tokens as they are generated.
Streaming is also the way to run long generations. Non-streaming requests are subject to a server-side time limit and return
HTTP 504(gateway_timeout) if they run too long; streaming requests are exempt because they return output incrementally. For long or large workloads, stream the response or run it in the background.
pythonimport osfrom openai import OpenAIclient = OpenAI(base_url="https://api.meta.ai/v1",api_key=os.environ["MODEL_API_KEY"],)with client.responses.stream(model="muse-spark-1.3",input="Explain how neural networks learn.",) as stream:for event in stream:if event.type == "response.output_text.delta":print(event.delta, end="", flush=True)print()
typescriptimport OpenAI from 'openai';const apiKey = process.env.MODEL_API_KEY;if (!apiKey) {throw new Error('MODEL_API_KEY is not set');}const client = new OpenAI({baseURL: 'https://api.meta.ai/v1',apiKey,});const stream = client.responses.stream({model: 'muse-spark-1.3',input: 'Explain how neural networks learn.',});for await (const event of stream) {if (event.type === 'response.output_text.delta') {process.stdout.write(event.delta);}}process.stdout.write('\n');
pythonimport jsonimport osimport requestsresponse = requests.post("https://api.meta.ai/v1/responses",headers={"Authorization": f"Bearer {os.environ['MODEL_API_KEY']}","Content-Type": "application/json","Accept": "text/event-stream",},json={"model": "muse-spark-1.3","input": "Explain how neural networks learn.","stream": True,},stream=True,)response.raise_for_status()for raw_line in response.iter_lines(decode_unicode=False):if not raw_line:continueline = raw_line.decode("utf-8", errors="replace")if not line.startswith("data: "):continuedata = line.removeprefix("data: ")if data == "[DONE]":breakevent = json.loads(data)if event.get("type") == "response.output_text.delta":delta = event.get("delta")if delta:print(delta, end="", flush=True)print()
shellcurl -N -X POST "https://api.meta.ai/v1/responses" \-H "Authorization: Bearer $MODEL_API_KEY" \-H "Content-Type: application/json" \-H "Accept: text/event-stream" \-d '{"model": "muse-spark-1.3","input": "Explain how neural networks learn.","stream": true}'
You can stream with previous_response_id for multi-turn. The streamed response still returns an id you can chain into subsequent turns.
The stream delivers typed server-sent events. Common types include response.created, response.in_progress, response.output_item.added / response.output_item.done, response.content_part.added / response.content_part.done, response.output_text.delta, and response.completed. When the model invokes tools, arguments stream as response.function_call_arguments.delta events and finalize with response.function_call_arguments.done. The model's raw reasoning is not streamed as text: Muse Spark emits no response.reasoning_text.* events and the reasoning output item carries no visible content. If you request a reasoning summary (reasoning.summary), the summary streams as response.reasoning_summary_text.delta / .done events. The raw chain of thought is available only in encrypted form for replay across turns, via include: ["reasoning.encrypted_content"]. If the response fails during streaming, the terminal event is response.failed instead of response.completed. It carries the response object with status: "failed" and a non-null error object with a code such as server_error and a sanitized message. See Error handling → Streaming errors.
Output items carry a status that transitions during the stream: "in_progress" on output_item.added, then "completed" on output_item.done. This applies to both message and function_call items. Third-party SDKs such as the Vercel AI SDK rely on status: "completed" as a terminal signal. Do not discard items before they reach this state.
Persistence guarantees
When store=true (the default), streaming responses are persisted even if the client disconnects mid-stream. The server continues processing after disconnect and stores the final result. You can safely use previous_response_id to reference a streamed response even after a network interruption. The response remains available for subsequent turns.
The server persists the response before it emits the terminal response.completed event, so once you have observed completion you can immediately retrieve the response or chain it with previous_response_id. In rare cases — server under heavy load, a rolling deploy, or a slow storage write — persistence can briefly lag the terminal event; an immediate GET /v1/responses/{id} may then return HTTP 404 (or a chained previous_response_id may return HTTP 400) for a few seconds. If you fetch or chain a just-completed response and hit that, retry once or twice with a short backoff.
Canceling a response
Cancel an in-progress background response by posting to the cancel endpoint with its id.
pythonimport osfrom openai import OpenAIclient = OpenAI(base_url="https://api.meta.ai/v1",api_key=os.environ["MODEL_API_KEY"],)response = client.responses.cancel("resp_abc123")print(response.model_dump_json(indent=2))
typescriptimport OpenAI from 'openai';const apiKey = process.env.MODEL_API_KEY;if (!apiKey) {throw new Error('MODEL_API_KEY is not set');}const client = new OpenAI({baseURL: 'https://api.meta.ai/v1',apiKey,});const response = await client.responses.cancel('resp_abc123');console.log(JSON.stringify(response, null, 2));
pythonimport jsonimport osimport requestsresponse = requests.post("https://api.meta.ai/v1/responses/resp_abc123/cancel",headers={"Authorization": f"Bearer {os.environ['MODEL_API_KEY']}"},)response.raise_for_status()print(json.dumps(response.json(), indent=2))
shellcurl -X POST "https://api.meta.ai/v1/responses/resp_abc123/cancel" \-H "Authorization: Bearer $MODEL_API_KEY"
json{"id": "resp_abc123","object": "response","status": "cancelled","model": "muse-spark-1.3","output": []}
The cancel endpoint handles races gracefully. If the response completes at the same moment a cancel arrives, the server returns the completed response instead of an error. The same applies when the response has already failed or been cancelled. You always get the current state back. If the response has been deleted, the cancel endpoint returns HTTP 404 (not-found).
Deleting a response
Delete a response by its ID with DELETE /v1/responses/{response_id}. The response is soft-deleted and can no longer be retrieved or referenced by previous_response_id.
A repeat DELETE on an already-deleted response returns HTTP 404. This is not idempotent. Once deleted, subsequent DELETE calls will not return 200.
Using with other features
Several Model API features work through the Responses API:
- Search grounding: Add
tools=[{"type": "web_search"}]to ground responses with real-time web results and inline citations. See Search grounding. - Image understanding: Send images as base64 data URLs or uploaded file references for visual analysis. See Image understanding.
- Log probabilities: Not supported. Muse Spark is a reasoning model, so requesting them via
include: ["message.output_text.logprobs"]returnsHTTP 400. The Responses API does not accept a top-levellogprobsparameter. - Reasoning effort: Control how much reasoning the model performs by setting
reasoning.effort. Accepted values:"none","minimal","low","medium","high","xhigh". When omitted, the model reasons by default. Setreasoning.effortto control how much."none"(disable reasoning) is not supported by Muse Spark and returnsHTTP 400. The Chat Completions equivalent is the top-levelreasoning_effortparameter. - Prompt cache retention: Set
prompt_cache_retentionto"in_memory"or"24h"as a hint for the server's prompt cache. Actual retention is managed server-side. - Safety identifiers: Set
safety_identifierto a stable, privacy-preserving identifier for the end user behind a request, so enforcement can be targeted at that user rather than your whole application. See Safety identifiers. - Sampling:
temperatureandtop_pare accepted (mirroring Chat Completions), but Muse Spark is tuned to run at the defaults (temperature=1.0,top_p=1.0) and performs best there. Leave both unset for most workloads. Adjusttemperatureortop_p, not both. To make output more focused or repeatable, prefer clearer instructions over loweringtemperature. - Sampling penalties: Set
frequency_penaltyorpresence_penalty(number, -2 to 2, default0) to reduce verbatim repetition or nudge the model toward new topics. Both mirror their Chat Completions behavior. - Include fields: Use
includeto request optional output fields such as logprob details. Accepted values mirror OpenAI's. Sending an unknown value returnsHTTP 400. - Streaming usage: With
stream: true, the final streamed response always includes usage.stream_options.include_usageis a Chat Completions option and has no effect on the Responses API. You do not need to set it. - Background responses: Set
background: trueto run a response asynchronously, then poll, stream-retrieve, or cancel it byid. The immediate acknowledgement includes"background": trueand"status": "queued"so you can confirm acceptance before polling. The creation request cannot combinebackground: truewithstream: true(returnsHTTP 400), but you can retrieve a background response as an SSE stream withGET /v1/responses/{id}?stream=true. Starting background responses is additionally rate-limited per team, see Rate limits. A background creation body is also capped at 1 MiB (1,048,576 bytes). A larger body returnsHTTP 413(payload_too_large). This is much smaller than the 50 MB inline-media limit, so if you inline large base64 content, reference it byfile_idfrom the Files API instead once you switch a request to background. See Error handling → 413. - Output-token limit:
max_output_tokensbounds tokens generated (reasoning plus visible output) and has a minimum of 16. It shares the model's context window with input:input_tokens + max_output_tokensmust fit within the context window. Requesting more than the model's configured maximum returnsHTTP 400. Do not set it to the full context window when the request has any input because the two share one budget.
pythonimport osfrom openai import OpenAIclient = OpenAI(base_url="https://api.meta.ai/v1",api_key=os.environ["MODEL_API_KEY"],)response = client.responses.create(model="muse-spark-1.3",input="Who won the most recent Formula 1 race?",tools=[{"type": "web_search",},],)print(response.model_dump_json(indent=2))
Safety identifiers
A safety identifier attributes a request to the end user behind it, so harmful behavior can be traced to an individual rather than your whole application. Adding a safety identifier is optional but highly recommended if your application handles requests from end users you can identify.
Set safety_identifier to a stable value that uniquely identifies the user in your system, up to 64 characters. Use a privacy-preserving value, such as a hash of an internal user ID, so you do not send usernames, emails, or other identifying information.
Python (OpenAI SDK)import osfrom openai import OpenAIclient = OpenAI(base_url="https://api.meta.ai/v1",api_key=os.environ["MODEL_API_KEY"],)response = client.responses.create(model="muse-spark-1.3",input="Hello!",safety_identifier="a1b2c3d4", # e.g. a hash of your internal user ID)
safety_identifier supersedes the deprecated user field. If you send both, safety_identifier takes precedence. Use prompt_cache_key to group requests for cache efficiency, which the user field previously handled. This applies equally to Chat Completions; on the Messages API the Anthropic-native equivalent is metadata.user_id.
OpenAI compatibility notes
The Responses API uses text.format for structured output, not response_format. Compatibility parsing may accept OpenAI-only top-level fields such as response_format, but they do not configure structured output on Responses. See structured output.
Some OpenAI parameters (stop, verbosity, logit_bias, prediction, n > 1, audio modalities, and web_search_options) are not implemented on the Responses endpoint. Omit them. Where accepted for compatibility, they have no effect. See Chat completion → OpenAI compatibility notes for related Chat Completions behavior.
Next steps
Now that you can carry reasoning across turns, layer on more building blocks. Control depth and summaries with reasoning, add cited real-time results with search grounding, and wire multi-step tool loops with tool calling. When you are ready, check the Responses API reference for the full schema.