Responses
Use the OpenAI Agents SDK, continue conversations, and handle tools and errors.
POST https://api.paperinstruments.com/v1/responses
Authorization: Bearer $PAPERINSTRUMENTS_API_KEY
Content-Type: application/jsonUse the OpenAI SDK's responses client. Requests use the
Responses format
with the limits below. Unsupported fields return 400; error.param identifies
the field.
Create a response
Install openai and export PAPERINSTRUMENTS_API_KEY as in the
quickstart.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.paperinstruments.com/v1",
api_key=os.environ["PAPERINSTRUMENTS_API_KEY"],
)
response = client.responses.create(
model="ultramarine-1-flash",
instructions="Answer in two sentences.",
input="Explain EBITDA.",
reasoning={"effort": "max"},
max_output_tokens=8192,
store=False,
)
print(response.status)
print(response.output_text)OpenAI Agents SDK
Use the OpenAI Agents SDK
with a Responses model connected to Ultramarine. Export PAPERINSTRUMENTS_API_KEY
as in the quickstart, then install the packages for your language.
Run this example from a directory containing README.md.
pip install openai-agents openaiimport asyncio
import os
from pathlib import Path
from agents import (
Agent, ModelSettings, OpenAIResponsesModel, RunConfig, Runner, function_tool,
)
from openai import AsyncOpenAI
from openai.types.shared import Reasoning
@function_tool
def read_readme() -> str:
"""Read the project's README.md."""
return Path("README.md").read_text(encoding="utf-8")
async def main():
async with AsyncOpenAI(
base_url="https://api.paperinstruments.com/v1",
api_key=os.environ["PAPERINSTRUMENTS_API_KEY"],
) as client:
agent = Agent(
name="Project assistant",
instructions="Read README.md with the tool before answering.",
model=OpenAIResponsesModel(
model="ultramarine-1-flash", openai_client=client,
),
model_settings=ModelSettings(
reasoning=Reasoning(effort="max"),
max_tokens=8192,
store=False,
extra_body={"include": []},
),
tools=[read_readme],
)
config = RunConfig(tracing_disabled=True)
result = await Runner.run(agent, "Summarize README.md.", run_config=config)
print(result.final_output)
follow_up = await Runner.run(
agent,
result.to_input_list() + [
{"role": "user", "content": "How do I run the project locally?"}
],
run_config=config,
)
print(follow_up.final_output)
asyncio.run(main())The SDK executes the function tool and sends its result back to Ultramarine.
With store: false, pass the full history on each follow-up using
result.to_input_list() in Python or result.history in Node.js.
Keep include empty because Ultramarine rejects encrypted reasoning.
Use the agent's instructions field for guidance; developer messages are unsupported.
The examples disable OpenAI tracing, so you only need your Paper Instruments API key.
Parameters
| Parameter | Accepted values |
|---|---|
model | Required. ultramarine-1-flash. |
input | A string or an array of Responses input items. Default: []. See input and tools. |
instructions | Optional string. Send instructions again on subsequent requests. |
reasoning.effort | low, high, or max. Default: max, which we recommend for best results. Reasoning cannot be disabled. |
max_output_tokens | Integer, 1–32,768, including reasoning. Omit to use the model's output ceiling. |
store | Boolean. Default: false, unlike OpenAI's true. Set true to save a response for retrieval and continuation. |
previous_response_id | ID of a stored response from your organization. Requires store: true. |
stream | Boolean. Default: false. Use true for typed server-sent events. |
tools | Array of function, mcp, or web_search definitions. See below. |
tool_choice | auto, none, required, or {"type":"function","name":"your_function"}. Named choices must match a supplied function. |
parallel_tool_calls | Boolean. |
max_tool_calls | Integer, 1–15; default: 4. Only accepted with MCP or web search. |
text.format | {"type":"text"}, {"type":"json_object"}, or json_schema with name, schema, optional description, and optional strict. |
temperature, top_p | Numbers: 0–2 and 0–1, respectively. Start with defaults. |
metadata | Up to 16 string pairs, or 15 when using previous_response_id. Keys: at most 64 characters; values: at most 512. |
prompt_cache_key | Optional string cache hint. Cache hits are not guaranteed. |
background, truncation, include, stream_options | Only false, "disabled", [], and {}, respectively. Omit these fields unless needed. |
Request bodies are limited to 20 MiB. Each request produces one response.
Input and tools
Message roles are system, user, and assistant. Text can be a string or
input_text parts:
{
"input": [
{"role": "user", "content": [{"type": "input_text", "text": "Explain EBITDA."}]}
]
}You can replay assistant output_text and refusal parts, plaintext reasoning
before its assistant message or function call, and function_call items.
Images, audio, files, encrypted reasoning, and
item references are unsupported.
Function definitions use the flat Responses shape:
{
"type": "function",
"name": "lookup_company",
"description": "Look up a company by ticker.",
"parameters": {
"type": "object",
"properties": {"ticker": {"type": "string"}},
"required": ["ticker"],
"additionalProperties": false
},
"strict": true
}Your application executes functions. Read function_call items in output,
parse their arguments, then submit a function_call_output item with the
matching call_id and a string output. With store: false, send the full
history, including prior output items and a result for every function call.
With stored continuation, send the new results and previous_response_id.
strict: true requires every object schema to set additionalProperties: false
and list all properties in required. strict: false preserves your schema.
When omitted, compatible schemas are normalized to strict mode.
Deferred tool loading is unsupported; omit defer_loading or set it to false.
Hosted tools require service availability; otherwise requests return 503
hosted_tools_not_configured. Accepted definitions:
| Tool | Definition and limits |
|---|---|
| Web search | {"type":"web_search"}. Additional search options are unsupported. |
| MCP | type: "mcp", a non-empty server_label, and an HTTPS server_url. Optional headers string map and allowed_tools array of names. One server per request. require_approval must be "never"; omission uses "never". |
Function and hosted tools can appear in the same tools array.
Continue a conversation
These examples reuse client from above. Set store: true on both requests:
first = client.responses.create(
model="ultramarine-1-flash", input="Remember that the project is called Atlas.",
store=True,
)
second = client.responses.create(
model="ultramarine-1-flash", input="What is the project called?",
store=True, previous_response_id=first.id,
)
print(second.output_text)
stored = client.responses.retrieve(second.id)
print(stored.usage)
client.responses.delete(second.id)Retrieval and deletion use GET and DELETE /v1/responses/{id} and do not add
generation usage. IDs from store: false cannot be retrieved or continued.
For application-managed history, use store: false, omit previous_response_id,
and send all history in input. See the OpenAI Agents SDK example
for an agent that manages history this way.
Streaming and usage
Set stream: true. These examples reuse client from above:
stream = client.responses.create(
model="ultramarine-1-flash", input="Explain EBITDA.", stream=True,
)
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
elif event.type in ("response.completed", "response.incomplete", "response.failed"):
print(event.response.status, event.response.usage)Check status, including after HTTP 200. Output limits return incomplete
with incomplete_details.reason: "max_output_tokens", including when the context
window fills first. A function call cut off this way is incomplete; do not run it. A failure can return
failed with an error, or interrupt a stream. A closed stream without a
terminal event does not establish completion.
Read usage.input_tokens, usage.input_tokens_details.cached_tokens,
usage.output_tokens, usage.output_tokens_details.reasoning_tokens, and
usage.total_tokens. Cached input is a subset of input; reasoning is a subset
of output. Stream usage is in the terminal response event; unknown usage is
null. Stored retrieval returns the same generation's usage.
Conversation caching
The x-session-id header is optional. You can use it if you want sticky
routing. When using it, reuse the same value for every turn, retry, and tool
result in a conversation, and use a different value for each conversation.
In Python, pass extra_headers={"x-session-id": "your-conversation-id"} to
client.responses.create. In Node.js, pass
{ headers: { "x-session-id": "your-conversation-id" } } as its second argument.
Keep repeated prompt prefixes stable. Cache hits depend on matching prefixes and cache availability.
Unsupported features and errors
Errors use the OpenAI envelope. Ultramarine's error
codes and validation rules below are not all standard OpenAI Responses behavior.
Errors also include error.request_id; responses carry x-request-id.
| Request | HTTP status / error.code | error.param |
|---|---|---|
background: true | 400 / unsupported_parameter | background |
conversation, non-empty include, encrypted reasoning, automatic truncation, non-empty stream_options | 400 / unsupported_parameter | The field or indexed item. |
developer messages, non-text content, unsupported input items | 400 / unsupported_parameter | The input field, such as input[0].role. |
file_search, code_interpreter, image_generation, or other unsupported tool types | 400 / unsupported_parameter | tools[i].type |
| Multiple MCP servers, approval callbacks, extra web-search options, or deferred tool loading | 400 / unsupported_parameter | tools or the tool field. |
Chat Completions fields (messages, reasoning_effort, response_format, max_tokens, n), state, or other unknown fields | 400 / unsupported_parameter | The unsupported field. |
| Unsupported effort, out-of-range values, malformed schemas, or unmatched function results | 400 / invalid_value | The invalid field. |
previous_response_id with store: false | 400 / invalid_value | store |
max_tool_calls without MCP or web search | 400 / unsupported_parameter | max_tool_calls |
| Response listing, input-item listing, or cancellation | 400 / unsupported_parameter | response_id |
| Compaction or input-token counting | 400 / unsupported_parameter | operation |
| Invalid, expired, deleted, unstored, or inaccessible response ID | 404 / response_not_found | response_id or previous_response_id |
| Hosted tools unavailable | 503 / hosted_tools_not_configured | null |
| Storage or hosted execution unavailable | 503 / fireworks_responses_unavailable, no_capacity, or response_deployment_unavailable | null |
For example, background: true returns:
{
"error": {
"type": "invalid_request_error",
"code": "unsupported_parameter",
"message": "Background execution is not supported. Set background=false; use stream=true for incremental output.",
"param": "background",
"request_id": "req_..."
}
}A hosted-tool call limit returns status: "incomplete" with
incomplete_details.reason: null and the extension
incomplete_details.provider_reason: "max_tool_calls".
See Errors for authentication, billing, rate limits,
upstream failures, and retry handling.