Open-model compatible clients
Updated
- Set an output budget
- Where the model's thinking arrives
- Prompt caching and session affinity
- Framework configuration
- Compatibility behavior
If your code already speaks the chat API, three values change and nothing else does.
| Connection | Value |
|---|---|
| API base | https://api.runinfra.ai/v1 |
| Workspace key | rp_... |
| Process variable | RUNINFRA_GATEWAY_KEY |
| Billing | The existing funding rules of your open-model account. |
Replace MODEL_ID with an available id from Open models; read current capabilities, limits and prices from GET /v1/models.
The model names used by the OpenAI API do not alias to model ids in the open-models service. Pass a model id from GET /v1/models. Streaming, tools, and structured output use the same request and response shapes where the selected model's page lists that capability. Streams terminate with data: [DONE], tool calls appear on the assistant message, and usage appears on the response.
Set an output budget
This is the one setting that decides whether a reasoning model has room to answer. Reasoning tokens count toward max_tokens and, on a billable response, are billed at the output rate. Set the first examples' max_tokens to the minimum of 16,384 and the model's live output limit. Up to 2,048 tokens can be spent on reasoning before any answer.
Where a recommended minimum is published, the model's page in the Model Library carries it. You pay for tokens generated, not for the ceiling you set, so a generous budget costs nothing extra. If the answer comes back empty, check finish_reason: "length" means raise the budget, not that the model produced nothing.
For a model whose own page recommends at least 4,096 output tokens, start at that minimum if the live limit permits it. The source measurements from August 14, 2026 found that a 2,048-token budget could be fully consumed by reasoning, leaving an empty or truncated answer; a 16,384-token budget left more room for the answer. Those historical probes billed output. Under the current usage contract of the open-models service, a response with no answer settles at zero.
Where the model's thinking arrives
The open-models service emits the model's thinking as reasoning on the message and on each stream delta. Some chat-compatible clients and providers use the name reasoning_content instead, so a client that reads only that name sees the answer but not the thinking. Measured per client:
| Client | Visible answer | Model's thinking |
|---|---|---|
AI SDK (@ai-sdk/openai-compatible) | text / text-delta | reasoningText, and reasoning-delta parts |
| Chat API SDK, Node and Python | message.content / delta.content | message.reasoning / delta.reasoning |
| The routing client | message.content | message.reasoning_content |
The chain client (langchain-openai) | content | not exposed, dropped by the integration |
Plain fetch | delta.content | delta.reasoning |
The answer is correct in every client above. Only access to the thinking differs, so pick accordingly if you want to render it.
Prompt caching and session affinity
Prefix caching is automatic server behavior on models with a published cached-input price. You do not select cache placement, retention, or reuse per request. On /v1/chat/completions, prompt_cache_options and prompt_cache_retention are rejected with 400 hosted_. On /v1/responses, prompt_cache_retention is accepted and has no effect, and prompt_cache_options is accepted only as null.
For explicit session routing, send x-session-id. For multi-turn conversations and agent sessions, give repeat requests a consistent session identity so the requests stay close to their cached prefix:
- Header:
x-session-id: <your-(conversation- or- task- id> x-parent-session-idand the legacyx-session-affinityare also accepted) - Body field:
prompt_cache_keywhen no explicit session identity is present.useris forwarded to the model and is not a session identity by itself.
These session-routing hints work on /v1/chat/completions and /v1/responses; they help keep the session's requests and cached prefix together. Session headers stay inside the gateway; prompt_cache_key is consumed before forwarding, so neither routing hint reaches the model. Without an explicit identity, requests may be grouped by their opening messages; a request without message text keeps workspace-level grouping. An explicit identity makes grouping exact for your conversation or task. See Sessions for placement precedence and the separate role of user. Read cached-token counts and hit rates in Prompt caching, and any published hit-rate figures on the selected model's page and the pricing page.
Framework configuration
The source examples selected models listed as available on September 21, 2026. These framework integrations returned complete answers from https://api.runinfra.ai/v1 in checks on 2026-08-14. These are client patterns from historical checks. Replace MODEL_ID with a currently available model and verify its required capabilities. Versions used: ai 7.0.65, @ai-sdk/openai-compatible 3.0.30, openai 7.4.0 (Node) and 2.54.0 (Python), langchain-openai, litellm.
The chain client
The routing client
The routing client reaches the open-models service through its openai/ provider prefix, and exposes the thinking as reasoning_content.
Add stream=True for deltas, reading chunk. and skipping the usage frame, which has no choices.
The indexing client
Use OpenAILike from llama-, with is_chat_model=True. The OpenAI class raises Unknown model for any id it does not list.
AI SDK
@ai-sdk/openai-compatible reads reasoning_content and falls back to reasoning, which is the name we emit, so both the answer and the thinking come through.
generateText works the same way, with the thinking on reasoningText.
maxOutputTokens is the AI SDK's name for the output budget above.
usage. reports 0 even when the model reasoned, because this endpoint does not yet break reasoning out of completion_tokens. Total outputTokens is correct and is what you are billed for.
Plain fetch, no SDK
A data: line can be split across two network reads, so the trailing partial line stays in buffer rather than being parsed. Dropping that carry is the usual cause of intermittent JSON.parse failures on a stream that works in testing.
Compatibility behavior
Which endpoints exist
The model APIs serve POST /v1/chat/completions, POST /v1/responses, POST /v1/messages, POST /v1/messages/count_tokens, GET /v1/models, GET /v1/models/{model}, GET /v1/credits, and GET /v1/usage. Chat completions and Responses use the chat-compatible contract. Messages and token counting use the messages-compatible contract.
Not supported anywhere: /v1/completions (legacy, non-chat), /v1/files, /v1/assistants, /v1/threads, and /v1/batches. /v1/responses is a chat-completions compatibility adapter and does not implement state, hosted tools such as web search and file search, conversation storage, or background jobs.
On a model whose page lists tool calling, function tools work end to end on /v1/responses. Declare tools flat with type, name and parameters in the Responses shape, or nested under function in the chat shape. Calls come back as function_call output items with call_id, name, and arguments. Send results back as function_call_output input items with the matching call_id, as a string or content-part array, and resend prior output items in input because the endpoint is stateless.
Streams carry the full item lifecycle: response.created, response.in_progress, response.output_item.added, response., text and function_call_arguments deltas, the matching done events, and response.completed, which ends every stream. Clients that build items from output_item.done work unchanged. stream_options: {"include_obfuscation": true} adds random padding to each delta.
For the open-models service, store is accepted and always treated as false; nothing is retrievable later. metadata, include (empty or any documented value; only reasoning. can add output here), truncation ("auto" or "disabled"), parallel_tool_calls, service_tier, client_metadata, safety_identifier, stream_options, max_tool_calls, and prompt_cache_retention are accepted; the hints among them have no effect. Input is never truncated: a prompt longer than the context window returns context_length_exceeded.
previous_response_id, conversation, and prompt are accepted only as null, and background only as false or null; other values are refused with the reason.
On /v1/responses, output_text carries the final answer only. The model's thinking arrives as its own reasoning output item ahead of the message, with raw text under content as reasoning_text parts and an empty summary. On streams it arrives as response. and response. events inside a response. and response.content_part.done pair. A completion that spends its whole token budget thinking returns an empty output_text with thinking preserved in the reasoning item, so raise max_output_tokens if the model limit permits it.
A reply that max_output_tokens or a content filter cut short is not whole: status is "incomplete" and incomplete_details.reason is max_output_tokens or content_filter. A non-streaming body carries it directly. A stream still ends on response.completed, never on response.incomplete; the response inside that event carries the same status and reason. Check status before treating an answer as complete. Item status is advisory: only the item being written when the reply stopped is incomplete, so parse a function call's arguments before running it.
See the Responses reference for the complete endpoint contract.
What happens to a field you send
Fields on the forwarding allowlist reach the model unchanged. Fields that are not on it are dropped before the request leaves the gateway, silently rather than as an error, so a request shape that works today keeps working.
Chat completions lists the allowlist field by field, and names the few that carry gateway behavior. It is the one authoritative list, so this page does not restate it.
Errors and retries
These chat-compatible routes use the chat API envelope, { error: { message, type, code } }, with six error.type values: invalid_request_error, authentication_error, permission_error, not_found_error, rate_limit_error, and api_error. There is no server_error type; 5xx responses carry api_error. The Messages routes use the Messages protocol envelope. Every status and code is in Errors, and the triage is in Troubleshooting.
Retry behavior belongs to your client configuration. Do not blindly retry a charge-bearing request after a partial stream may have reached your app; use an idempotency key instead. Quote the X-Request-Id response header on any support ticket.