Most migrations are a base URL + API key swap. The compat tree at /v3/compat/... is shaped so that pointing a stock provider SDK at https://api.opper.ai/v3/compat works without changing your call sites.
For exact provider-side payload shapes, follow each provider's own spec. This file documents only what differs at the Opper boundary — plus the one migration that isn't a base-URL swap: moving off Opper's own legacy /call.
/call (Opper's task-completion endpoint, also what the legacy v2 Python SDK's opper.call(...) hits) is being sunset. The replacement is a compat chat endpoint with structured output as a parameter — same models, same tracing and control plane, standard OpenAI shape. Don't point new integrations at /call; migrate existing ones.
Before — a typical structured /v3/call:
curl -s -X POST https://api.opper.ai/v3/call \
-H "Authorization: Bearer $OPPER_API_KEY" -H "Content-Type: application/json" \
-d '{
"name": "extract_room_details",
"instructions": "Extract details about the room described in the text.",
"model": "openai/gpt-5-mini",
"input": {"text": "The corner office on floor 3 fits 8 people and has a projector."},
"output_schema": {"type": "object", "properties": {
"floor": {"type": "integer"},
"capacity": {"type": "integer"},
"equipment": {"type": "array", "items": {"type": "string"}}
}, "required": ["floor", "capacity", "equipment"]}
}'After — the same task on /v3/compat/chat/completions:
curl -s -X POST https://api.opper.ai/v3/compat/chat/completions \
-H "Authorization: Bearer $OPPER_API_KEY" -H "Content-Type: application/json" \
-H "X-Opper-Name: extract_room_details" \
-d '{
"model": "openai/gpt-5-mini",
"messages": [
{"role": "system", "content": "Extract details about the room described in the text."},
{"role": "user", "content": "The corner office on floor 3 fits 8 people and has a projector."}
],
"response_format": {"type": "json_schema", "json_schema": {
"name": "room_details",
"schema": {"type": "object", "properties": {
"floor": {"type": "integer"},
"capacity": {"type": "integer"},
"equipment": {"type": "array", "items": {"type": "string"}}
}, "required": ["floor", "capacity", "equipment"]}
}}
}'Field-by-field:
/v3/call |
Compat chat completions |
|---|---|
name |
X-Opper-Name request header — keeps named-function tracing and guardrail function-scope filtering |
instructions |
system message |
input |
user message — JSON-encode structured input into the message content |
output_schema |
response_format: {type: "json_schema", json_schema: {name, schema}} |
model (string or fallback array) |
model — same provider/model form; for fallback chains, define a Route alias in the platform |
response data |
choices[0].message.content — a JSON string; parse it |
meta.cost |
usage.opper.cost.total in the body, and the X-Opper-Cost response header |
meta.trace_uuid |
meta.trace_uuid — traces work identically, visible at platform.opper.ai |
In Python, the legacy SDK call maps onto the stock OpenAI SDK:
# Before (legacy opper.call)
result = opper.call(
"extract_room_details",
instructions="Extract details about the room described in the text.",
input={"text": "..."},
output_schema=RoomDetails, # Pydantic model or JSON Schema
)
room = result.data
# After (OpenAI SDK against /v3/compat)
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.opper.ai/v3/compat",
api_key=os.environ["OPPER_API_KEY"],
default_headers={"X-Opper-Name": "extract_room_details"},
)
resp = client.chat.completions.parse( # .parse() takes the Pydantic model directly
model="openai/gpt-5-mini",
messages=[
{"role": "system", "content": "Extract details about the room described in the text."},
{"role": "user", "content": "..."},
],
response_format=RoomDetails,
)
room = resp.choices[0].message.parsedNotes:
.parse()vs.create()— the OpenAI SDK'sparse()accepts a Pydantic model and returnsmessage.parsed, the closest ergonomic match toresult.data. With plaincreate()+ a rawjson_schema, parsechoices[0].message.contentyourself.strict: trueonjson_schemafollows OpenAI's rules: every property must be listed inrequiredand objects needadditionalProperties: false. Leavestrictoff to keep a/call-style loose schema.- Any compat surface works — Anthropic Messages with a forced tool schema, OpenAI Responses with
text.format, etc. Chat completions is the shortest hop from/call.
from openai import OpenAI
client = OpenAI(
base_url="https://api.opper.ai/v3/compat",
api_key=os.environ["OPPER_API_KEY"],
)
resp = client.chat.completions.create(
model="anthropic/claude-sonnet-4.6",
messages=[{"role": "user", "content": "Hello"}],
)That's it. Verify the model identifier with curl -s https://api.opper.ai/v3/models — provider-side names sometimes differ between gateways.
Same code change as OpenRouter — point base_url at https://api.opper.ai/v3/compat. The OpenAI SDK appends /chat/completions, /responses, /embeddings, all of which exist on the compat tree. Use Opper's provider/model identifier form.
For schema-constrained output, add response_format: {type: "json_schema"} to the compat call — no separate endpoint. For multi-model fall-back, define a Route alias in the platform that maps one name to an ordered backup chain.
Anthropic SDK users can point at the compat tree, but the Anthropic SDK sends x-api-key by default and Opper only accepts Authorization: Bearer. Override default headers:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
baseURL: "https://api.opper.ai/v3/compat",
apiKey: process.env.OPPER_API_KEY,
defaultHeaders: { Authorization: `Bearer ${process.env.OPPER_API_KEY}` },
});The Anthropic SDK appends /v1/messages, which resolves to /v3/compat/v1/messages — a real endpoint on the spec.
Same pattern: base URL → https://api.opper.ai/v3/compat, key → Opper key. For LiteLLM, the provider/model identifier shape already lines up.
- One bill, one key, one trace surface across providers.
- Multi-model fall-back via a Route alias (one name → an ordered backup chain).
- Multimodality (images, audio, video) and realtime voice behind the same key.
- Knowledge bases (on the v2 surface —
/v2/knowledge/...) and tracing in the same control plane.
curl -s https://api.opper.ai/v3/modelsreturns a list (no auth needed) — network and routing work.- A single chat / message round-trip succeeds against the compat endpoint with your Opper key.
- Streaming works (SSE format is provider-compliant; events use
data: ...and the stream ends with[DONE]). - Spans appear in the platform UI for those calls.
| For | Look at |
|---|---|
| Compat endpoints, deeper | compatibility.md |
| Live spec (v3) | https://api.opper.ai/v3/openapi.yaml |
| Media, realtime, roundtable endpoints | the opper-api SKILL.md |
| Coding assistant integrations | docs.opper.ai/overview/integrations |