Paper Instruments
Ultramarine API

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

Use 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 openai
import 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

ParameterAccepted values
modelRequired. ultramarine-1-flash.
inputA string or an array of Responses input items. Default: []. See input and tools.
instructionsOptional string. Send instructions again on subsequent requests.
reasoning.effortlow, high, or max. Default: max, which we recommend for best results. Reasoning cannot be disabled.
max_output_tokensInteger, 1–32,768, including reasoning. Omit to use the model's output ceiling.
storeBoolean. Default: false, unlike OpenAI's true. Set true to save a response for retrieval and continuation.
previous_response_idID of a stored response from your organization. Requires store: true.
streamBoolean. Default: false. Use true for typed server-sent events.
toolsArray of function, mcp, or web_search definitions. See below.
tool_choiceauto, none, required, or {"type":"function","name":"your_function"}. Named choices must match a supplied function.
parallel_tool_callsBoolean.
max_tool_callsInteger, 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_pNumbers: 0–2 and 0–1, respectively. Start with defaults.
metadataUp to 16 string pairs, or 15 when using previous_response_id. Keys: at most 64 characters; values: at most 512.
prompt_cache_keyOptional string cache hint. Cache hits are not guaranteed.
background, truncation, include, stream_optionsOnly 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:

ToolDefinition and limits
Web search{"type":"web_search"}. Additional search options are unsupported.
MCPtype: "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.

RequestHTTP status / error.codeerror.param
background: true400 / unsupported_parameterbackground
conversation, non-empty include, encrypted reasoning, automatic truncation, non-empty stream_options400 / unsupported_parameterThe field or indexed item.
developer messages, non-text content, unsupported input items400 / unsupported_parameterThe input field, such as input[0].role.
file_search, code_interpreter, image_generation, or other unsupported tool types400 / unsupported_parametertools[i].type
Multiple MCP servers, approval callbacks, extra web-search options, or deferred tool loading400 / unsupported_parametertools or the tool field.
Chat Completions fields (messages, reasoning_effort, response_format, max_tokens, n), state, or other unknown fields400 / unsupported_parameterThe unsupported field.
Unsupported effort, out-of-range values, malformed schemas, or unmatched function results400 / invalid_valueThe invalid field.
previous_response_id with store: false400 / invalid_valuestore
max_tool_calls without MCP or web search400 / unsupported_parametermax_tool_calls
Response listing, input-item listing, or cancellation400 / unsupported_parameterresponse_id
Compaction or input-token counting400 / unsupported_parameteroperation
Invalid, expired, deleted, unstored, or inaccessible response ID404 / response_not_foundresponse_id or previous_response_id
Hosted tools unavailable503 / hosted_tools_not_configurednull
Storage or hosted execution unavailable503 / fireworks_responses_unavailable, no_capacity, or response_deployment_unavailablenull

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.

On this page