OpenAI & Anthropic compatibility
Drop-in replacements for api.openai.com and api.anthropic.com — Chat Completions, the Responses API, and a native Messages endpoint that Claude Code talks to without a proxy.
Three wire protocols reach the same models, the same balance and the same usage log. Pick whichever your tooling already speaks.
| Protocol | Base URL | Auth header |
|---|---|---|
| OpenAI Chat Completions, Responses, Images, Embeddings & rerank, Models | https://api.gpuniq.com/v1/openai | Authorization: Bearer gpuniq_... |
| OpenAI Responses (bare path) | https://api.gpuniq.com/v1 | Authorization: Bearer gpuniq_... |
| Anthropic Messages | https://api.gpuniq.com | x-api-key or Authorization: Bearer |
Claude Code
Native /v1/messages — no LiteLLM, no proxy.
Cursor
Override the OpenAI base URL in settings.
Aider & friends
Anything that reads OPENAI_BASE_URL.
OpenAI Chat Completions
Point any OpenAI-compatible tool at GPUniq by setting two environment variables:
OPENAI_API_KEY=gpuniq_your_key
OPENAI_BASE_URL=https://api.gpuniq.com/v1/openai
Every field of the OpenAI Chat Completions protocol is forwarded unchanged: tools, tool_choice, response_format, logprobs, seed, stream, stream_options, etc.
Authentication takes either header on every /v1/openai/* route (and
/v1/responses): Authorization: Bearer gpuniq_…, the shape the SDKs send,
or X-API-Key: gpuniq_…, the shape the native API uses. A missing
credential is 401 authentication_required in the OpenAI error envelope.
Successful responses carry the X-RateLimit-* headers.
usage.cost is what you were charged
Every chat/completions response carries usage.cost: the amount GPUniq
debited for the call, in USD. On a stream it is the list-price estimate
(cached input included); the exact charge with your personal discount is in
GET /v1/llm/usage/requests/{id}. Upstream cost breakdowns are never
forwarded.
With ruble billing
on, a non-stream response also carries usage.cost_rub and
usage.fx_rate_rub: the same charge in rubles at the rate of the top-up the
money came from. Streams do not know the final charge yet; read it from
GET /v1/llm/usage/requests/{id}, where every row has cost_rub and
rub_pricing ("billed" or "market").
Models list
GET /v1/openai/models returns every id chat/completions accepts — the
GPUniq catalog and the external OpenRouter tier — in the OpenAI
{"object": "list", "data": [...]} shape. created is the vendor's release
timestamp where it is known; 1735689600 (2025-01-01) stands for "unknown".
Prices are not part of the OpenAI schema — read
GET /v1/llm/models/catalog for them.
Official OpenAI SDK
from openai import OpenAI
client = OpenAI(
api_key="gpuniq_your_key",
base_url="https://api.gpuniq.com/v1/openai",
)
resp = client.chat.completions.create(
model="claude-opus-4-7",
messages=[{"role": "user", "content": "Write a binary search in Rust."}],
)
print(resp.choices[0].message.content)
import OpenAI from "openai";
const client = new OpenAI({
apiKey: "gpuniq_your_key",
baseURL: "https://api.gpuniq.com/v1/openai",
});
const resp = await client.chat.completions.create({
model: "claude-opus-4-7",
messages: [{ role: "user", content: "Write a binary search in Rust." }],
});
console.log(resp.choices[0].message.content);
curl https://api.gpuniq.com/v1/openai/chat/completions \
-H "Authorization: Bearer gpuniq_your_key" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-4-7",
"messages": [{"role": "user", "content": "Write a binary search in Rust."}]
}'
Streaming
Set stream: true — GPUniq returns a text/event-stream with byte-identical OpenAI SSE framing:
stream = client.chat.completions.create(
model="gpt-5.2",
messages=[{"role": "user", "content": "Explain MoE in one paragraph."}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
Images
POST /v1/openai/images/generations matches OpenAI's images.generate
protocol and accepts any image slug in the catalog. It is a legacy
surface kept so OpenAI-SDK code runs unmodified: it holds the connection
open for the whole render and a client that disconnects mid-render is still
charged for an image it never receives.
For anything new use the job API. Full parameter reference, per-model shape tables and the migration recipe: Image generation.
The Responses API
POST /v1/responses (and POST /v1/openai/responses) implements OpenAI's
Responses protocol against the same catalog, for SDK code written against
client.responses.create.
from openai import OpenAI
client = OpenAI(
api_key="gpuniq_your_key",
base_url="https://api.gpuniq.com/v1",
)
resp = client.responses.create(
model="gpt-5.5",
input="Summarise the CAP theorem in three bullets.",
)
print(resp.output_text)
Anthropic Messages
GPUniq exposes a native Anthropic Messages API at POST /v1/messages —
not a translation layer bolted onto the OpenAI surface. It accepts the
standard Messages body (model, messages, system, max_tokens, tools,
stream) and, with stream: true, returns the Anthropic SSE event sequence
(message_start → content_block_* → message_delta → message_stop).
Errors come back in the Anthropic error shape, so the official SDK's typed
exceptions work unchanged.
import anthropic
client = anthropic.Anthropic(
api_key="gpuniq_your_key",
base_url="https://api.gpuniq.com",
)
msg = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Write a binary search in Rust."}],
)
print(msg.content[0].text)
Reach for this endpoint when the model is a Claude model and the request
carries tools. /v1/messages is Anthropic's own protocol, so tool
definitions and tool results travel without a translation step in the
middle — which is where tool round-trips are most likely to lose fidelity.
Claude Code
Claude Code talks to GPUniq directly — GPUniq exposes a native Anthropic Messages API at /v1/messages, so no LiteLLM (or any other) proxy is required. Point Claude Code's environment variables straight at GPUniq:
export ANTHROPIC_BASE_URL=https://api.gpuniq.com
export ANTHROPIC_API_KEY=gpuniq_your_key
export ANTHROPIC_MODEL=claude-opus-4-7 # main model
export ANTHROPIC_SMALL_FAST_MODEL=claude-haiku-4-5 # background/small model
claude
Use any Claude slug from /v1/openai/models for the two model variables. Streaming and tool use work out of the box. All tokens are billed against your GPUniq balance — no separate Anthropic account required.
Set ANTHROPIC_SMALL_FAST_MODEL too: Claude Code calls a smaller "background" model for things like commit messages and titles. If it points at a slug GPUniq doesn't serve, those background calls fail even when the main model works.
Cursor
Settings → Models → Override OpenAI Base URL:
Base URL: https://api.gpuniq.com/v1/openai
API Key: gpuniq_your_key
Model: claude-opus-4-7 # or any slug from /v1/openai/models
Continue.dev / Aider / LiteLLM
Any tool that accepts an OPENAI_BASE_URL works the same way:
export OPENAI_API_KEY=gpuniq_your_key
export OPENAI_BASE_URL=https://api.gpuniq.com/v1/openai
aider --model claude-sonnet-4-6
The OpenAI-compat endpoint returns raw OpenAI response objects (not wrapped in GPUniq's ResponseSchema). Errors use OpenAI's {"error": {"message", "type", "code"}} envelope so SDK retry logic works unchanged.