Chat completions
Request parameters, reasoning, tools, and streaming.
POST https://api.paperinstruments.com/v1/chat/completions
Authorization: Bearer $PAPERINSTRUMENTS_API_KEY
Content-Type: application/jsonWe reject unsupported request and message fields with 400; error.param
identifies the field.
Create a chat completion
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"],
)
stream = client.chat.completions.create(
model="ultramarine-1-flash",
messages=[{"role": "user", "content": "Explain EBITDA in two sentences."}],
reasoning_effort="max",
max_completion_tokens=8192,
stream=True,
)
for chunk in stream:
if chunk.choices:
print(chunk.choices[0].delta.content or "", end="", flush=True)Parameters
| Parameter | Notes |
|---|---|
model | Required. ultramarine-1-flash. |
messages | Required, non-empty array. Roles: system, user, assistant, tool. Content is a string or text and image_url parts; see images. Use system for instructions. We reject developer messages. |
reasoning_effort | low, high, or max. Default: max. |
max_completion_tokens | Integer, 1–32,768, including reasoning. max_tokens is an alias; send only one. Omit to use the model's output ceiling. |
stream | Boolean. Default: false. Use true for server-sent events. |
stream_options | Set {"include_usage": true} to receive stream usage. |
tools, tool_choice, parallel_tool_calls | Function tools and tool selection. Your application executes the calls. |
response_format | Structured output: json_object or json_schema, or plain text. |
temperature, top_p, top_k, min_p | Sampling controls. Start with defaults. |
presence_penalty, frequency_penalty, repetition_penalty, logit_bias | Token selection controls. |
logprobs, top_logprobs | Token probabilities. top_logprobs must be an integer, 0–20. |
n | Integer, 1–16. Default: 1. Each completion adds output usage. |
stop | A string or up to 16 non-empty strings, each at most 1,024 characters. Matched against content only, not reasoning_content. Generation continues after a match: those tokens are not returned but count in completion_tokens. |
seed | Sampling seed. |
prompt_cache_key, user | Optional cache hint and user identifier. See conversation caching. |
Limit request bodies to 20 MiB. We reject audio, legacy functions, store,
metadata, and service_tier.
Images
Send image_url parts with a base64 data URL or a public HTTP(S) URL:
{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": "data:image/png;base64,iVBORw0..."}},
{"type": "text", "text": "What does this chart show?"}
]
}Formats: PNG, JPEG, WebP, and GIF. Up to 16 images per request; each at most 10 MiB and 32 million pixels. Animated images are rejected. URL images must load within 10 seconds. Images count as input tokens.
Reasoning
We default to max thinking (reasoning effort) and recommend it for best results.
Test high or low to use fewer reasoning tokens.
The model requires reasoning. Set the top-level
reasoning_effort field. We return 400 for disabled thinking,
reasoning.effort, or effort values other than low, high, and max.
Read the answer from message.content and reasoning from
message.reasoning_content. During streaming, read delta.content and
delta.reasoning_content. Include reasoning and answer tokens in your output
budget. We charge both at the output rate.
Streaming and usage
Follow the streaming example. For a single JSON response,
set stream=false and read choices[0].message.content.
Set stream_options={"include_usage": true} to receive the final usage chunk.
It has choices: []; check for choices before reading a delta.
| Usage field | Counts |
|---|---|
prompt_tokens | All input tokens, including cached input. |
prompt_tokens_details.cached_tokens | Cached input, a subset of input. |
completion_tokens | All output tokens, including reasoning. |
completion_tokens_details.reasoning_tokens | Reasoning, a subset of output. |
total_tokens | Input plus output. |
A successful stream ends with [DONE]. An interrupted stream emits
data: {"error": ...} and ends without [DONE].
See errors for retry handling.
Output limits
- Output that reaches
max_completion_tokensreturnsfinish_reason: "length", including a tool call cut off mid-arguments. Do not run that call. - Input that fits but leaves less room than the limit runs until the context
window is full and returns
finish_reason: "length". - Input larger than the context window returns
400.
Tool calls
Define tools with type: "function" and a function containing name,
description, and JSON Schema parameters. Handle tool_calls as follows:
- Append the assistant message's
role,content,tool_calls, and anyreasoning_contentto your history. - Run each function with its parsed
function.arguments. - Append one
toolmessage per call, withtool_call_idset to the call'sidandcontentset to the result text. - Repeat the request with the updated
messagesand the sametoolsuntil the model returns an answer.
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"};
in Node.js, pass { headers: { "x-session-id": "your-conversation-id" } } as the
second argument to create. The header takes precedence over prompt_cache_key.
Keep repeated prompt prefixes stable. Cache hits depend on matching prefixes and cache availability. Send the full message history on each request.
GET /v1/models lists available models; GET /v1/models/{id} retrieves one.
Responses include x-request-id. Save it when reporting an error.