Sub-skill of
opper— start there for discovery and setup guidance. Source: https://github.com/opper-ai/opper-skills/blob/main/opper-api/SKILL.md
Opper API
Opper is a gateway in front of LLM providers plus a control plane for the things you build on top. The gateway is one connection to 300+ models across all major providers (OpenAI, Anthropic, Google, Mistral, …), EU-hosted and GDPR-compliant. The control plane covers five capabilities:
- Route — call any supported model through one key, no provider-specific SDKs or credentials.
- Observe — every call yields a trace with input, output, latency, cost, and model used; attach metrics and evaluations to track quality over time.
- Steer — improve quality through feedback loops; save good outputs as examples and build evaluation datasets.
- Guard — guardrails at the infrastructure level (PII removal, content filtering, budget limits) before data reaches the model.
- Comply — follows European data protection directives and security standards, including GDPR.
The HTTP API at https://api.opper.ai is the foundation — every SDK and the CLI talk to it.
Concepts: docs.opper.ai/overview/about. Quickstart: docs.opper.ai/overview/quickstart.
Pick your endpoint
One gateway, one key. Pick the endpoint by what you're building — the routing, governance, and tracing are the same underneath:
- Text generation / chat / migrating from OpenAI, Anthropic, Google, or OpenResponses: the compat endpoints under
/v3/compat/.... Point your SDK's base URL there, keep your code, swap the key. This is the main path. See references/compatibility.md and references/migration.md. - Structured output (typed JSON out of a model): the standard
response_format: {type: "json_schema"}on any compat chat endpoint — no separate endpoint. - Still on Opper's own
/call(POST /v3/call, or the legacy v2 Python SDK'sopper.call)? It's being sunset — migrate to a compat chat endpoint withresponse_format. The field-by-field mapping is in references/migration.md. - Media generation (images, audio / TTS / STT, video, OCR), files, and realtime voice: a separate surface — switch to the
opper-multimodalskill. Quick map: imagesPOST /v3/images, audioPOST /v3/audio/{speech,transcriptions}, videoPOST /v3/videos(async), OCRPOST /v3/ocr, files/v3/files, realtimewss://api.opper.ai/v3/realtime. - Roundtable (fan one prompt out to several models, then consolidate or compare):
POST /v3/roundtable(beta). - Building an agent (multi-step reasoning, tool use, multi-agent, MCP): switch to the
opper-sdksskill and use the Agent SDK —Agent,tool,Conversationship in the unifiedopperaipackage for both Python and TypeScript. - Knowledge bases / RAG: see "Knowledge bases" below — they live on
/v2/knowledge/....
The live v3 spec is the source of truth
Default workflow for any API question — endpoint, parameter, field, capability, compliance attribute, query flag — that isn't immediately answered by this skill: grep the spec. Don't guess, don't infer from docs pages, don't fall back to the browsable catalog. Both formats are unauthenticated and definitive:
curl -s https://api.opper.ai/v3/openapi.yaml # YAML, easier to grep
curl -s https://api.opper.ai/v3/openapi.json # JSON
Worked example — "which Opper models have ZDR?":
# 1. Grep the spec for the term.
curl -s https://api.opper.ai/v3/openapi.yaml | grep -i -n zdr
# → reveals compliance.zdr on every GET /v3/models item — no include, no API key
# 2. Call the endpoint and derive the answer from the facts.
curl -s https://api.opper.ai/v3/models \
| jq -r '.models[] | select(.compliance.zdr.logging == false and .compliance.zdr.moderation != true) | .id'
# Or print the facts per model and decide yourself.
curl -s https://api.opper.ai/v3/models | jq '.models[] | {id, zdr: .compliance.zdr}'
compliance.zdr is an object of five tri-state facts — logging, moderation, caching, training, subprocessors — each true (content is held / reaches that pipeline), false (it is not), or null (not established). There is no summary string; derive what you need. "ZDR by default" is logging == false and moderation != true. The same object sits on the opper block of /v3/compat/models items and, with a key, on the route object (?include=route, /v3/providers?include=routes).
Same pattern answers any "where is X" question — grep the spec, follow the schema to the endpoint, call it. (Knowledge bases live on a separate v2 surface — see "Knowledge bases" below.)
Authenticate
Authorization: Bearer $OPPER_API_KEY
Server URL: https://api.opper.ai (the /v3 or /v2 prefix is part of each path). Bearer is the only auth scheme — there is no x-api-key. Get a key at platform.opper.ai.
A few endpoints have security: [] and don't require a key: /health, /v3/openapi.yaml, /v3/openapi.json, /v3/models.
Canonical example — a compat chat completion
The main path is the OpenAI-compatible chat endpoint. The same shape works from any provider SDK pointed at /v3/compat.
curl -s -X POST https://api.opper.ai/v3/compat/chat/completions \
-H "Authorization: Bearer $OPPER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5-mini",
"messages": [{"role": "user", "content": "Hello!"}]
}'
The response is the provider's native shape (here OpenAI's chat.completion). Models are addressed provider/model; you can call any model from any compat surface.
Structured output
For typed JSON, add response_format — no separate endpoint:
curl -s -X POST https://api.opper.ai/v3/compat/chat/completions \
-H "Authorization: Bearer $OPPER_API_KEY" -H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5-mini",
"messages": [{"role": "user", "content": "Extract entities: Tim Cook announced Apple’s Austin office."}],
"response_format": {"type": "json_schema", "json_schema": {"name": "entities", "schema": {
"type": "object",
"properties": {
"people": {"type": "array", "items": {"type": "string"}},
"locations": {"type": "array", "items": {"type": "string"}}
},
"required": ["people", "locations"]
}}}
}'
The model's content comes back as a JSON string validated against the schema. Streaming: add "stream": true and read the text/event-stream (events prefixed data: , ending with [DONE]).
Models — GET /v3/models (no auth required)
One of the most common reasons to use this skill. The live endpoint is the most up-to-date list — never hardcode model names:
curl -s https://api.opper.ai/v3/models
Two URLs, two audiences:
- In code:
https://api.opper.ai/v3/models— programmatic discovery, returns JSON. - When talking to the user: opper.ai/models — the browsable human catalog. Link this when recommending or discussing a model.
The default response is light but every item carries a compliance block — residency, country, inference_location, zdr (the object above), training, logging, moderation, caching, content_storage, dpa_available, third_party_access, transfer_mechanism, route_id. That answers most ZDR / residency / GDPR questions without a key. For per-route evidence or benchmarks, add ?include=:
include= |
Adds | What lives there |
|---|---|---|
route (requires API key) |
route object per model |
The full service-route record: data_handling.{training,logging,moderation,caching} with retention_days / storage_location / human_review, data_handling.subprocessors_read_content, gdpr.{residency,dpa_available,transfer_mechanism}, verification.status, sources, last_verified_at, underlying_maker — plus the same zdr object as compliance.zdr |
maker (public) |
maker |
Original weights creator (e.g. meta for Llama, mistral for Mistral) |
benchmarks (public) |
benchmarks |
Public scores from artificialanalysis.ai |
Combine with commas, e.g. ?include=route,benchmarks. Reach for include=route when you need retention days, storage location, verification status, or the sources behind a claim — compliance is the summary, route is the evidence.
Filter by capability and type: ?capability=vision / ?capability=pdf (which chat models accept images / PDFs), ?type=image|tts|stt|video. The modality endpoints below also have scoped discovery lists.
References: docs.opper.ai/capabilities/models.
The model string — pools, pins, aliases and routes
GET /v3/models returns one row per provider, not per model. Four things can go in the model field, all without special headers or flags:
| Form | Example | What it addresses |
|---|---|---|
| Bare model name — a pool | "kimi-k3" |
Every provider serving that model. Opper picks one; the rest are failover |
| Provider-qualified id — a pin | "tensorx/moonshotai/kimi-k3" |
Exactly that one provider row, no failover |
| Org alias — your own list | "my-flash" |
An ordered list you defined: primary first, then fallbacks. CRUD at /v2/models/aliases |
| Route — a deployed graph | "dynamic/my-route" |
Classification, branching, per-node fallbacks, versioned. Built in the platform UI (or /management/v1/dynamic-routes with a Management API Key) — but calling one only needs your normal project key |
None of these is the default answer — pick per use case. Pin for determinism (evals, a guaranteed jurisdiction or price, a provider-specific behaviour); pool for redundancy without thinking about it; alias to name your own policy once and reuse it; route when the decision depends on the request.
On pools specifically: model is the group key on each row (and the bare name you call), pooled is whether that row answers to it. pooled: false rows are fully callable by explicit id — they're just held out of bare-name routing, usually for a smaller context window or a pricier latency-tuned variant.
Two things that surprise people:
- The response
modelfield echoes what you sent — a bare name, an alias, or adynamic/route all come back verbatim, so it never reveals which provider ran. The trace does:span.meta.modelcarries the resolvedprovider/provider_model_id. For routes, theX-Opper-Route-Modelresponse header answers it directly. - Identical requests with no
X-Opper-Trace-Idall land on the same provider. That's deliberate session affinity (keyed on your API key over a 30-minute window, for prompt-cache locality), not broken round-robin. Send a unique trace id per request to spread them.
Full mechanics — how the four compose, selection order, failover, alias and route management: references/model-routing.md.
Provider-compatible endpoints — under /v3/compat/...
Drop-in replacements for several LLM APIs. The simplest migration: point the SDK base URL at https://api.opper.ai/v3/compat, use your Opper API key, and your existing code keeps working. Fully compliant with each upstream provider — see their spec for unfamiliar payloads.
| Endpoint | Compatible with | Opper docs |
|---|---|---|
POST /v3/compat/chat/completions |
OpenAI Chat Completions | docs |
POST /v3/compat/responses |
OpenAI Responses | docs |
POST /v3/compat/openresponses |
OpenResponses | docs |
POST /v3/compat/v1/messages |
Anthropic Messages | docs |
POST /v3/compat/v1beta/interactions |
Google Interactions | docs |
POST /v3/compat/embeddings |
OpenAI Embeddings | (see spec) |
Curl seeds and SDK-rebasing tips: references/compatibility.md. Vision and PDF input ride on these endpoints too — send image_url / file content parts to a model whose capabilities include vision / pdf (see the opper-multimodal skill, and docs.opper.ai/build/multimodal/vision-pdfs).
Media generation, files & realtime → the opper-multimodal skill
Producing media (images, audio TTS/STT, video, OCR), the /v3/files storage API, sending images/PDFs into a model, and realtime two-way voice are a separate surface with their own dedicated endpoints. They share this gateway's key, governance, and tracing, but the request/response contracts differ — so they live in their own skill: opper-multimodal (SKILL.md).
Quick map (fetch the spec or that skill for shapes): images POST /v3/images (sync, opt-in async), audio POST /v3/audio/speech (TTS) + POST /v3/audio/transcriptions (STT), video POST /v3/videos (async — poll GET /v3/artifacts/{id}/status), OCR POST /v3/ocr, files /v3/files, realtime wss://api.opper.ai/v3/realtime (browser tickets via POST /v3/realtime-sessions). Each media surface has a scoped discovery list (GET /v3/images/models, /v3/videos/models, /v3/audio/models, /v3/ocr/models).
Server-side tools (opper:web_search and friends) are different — they ride the compat chat endpoints and stay in this skill; see "Server-side web search" below.
Roundtable — POST /v3/roundtable (beta)
Send one input to several models in parallel and bring the answers back together. Resolution modes: summary (default; a consolidation model merges them), fast (no consolidation, read model_results), or multiple_choice (vote among choices). Guide: docs.opper.ai/build/roundtable/overview.
Server-side web search
Run web search server-side (billed per search under usage.opper.cost.tools):
- Portable — add
{"type": "opper:web_search"}totools[]on any compat chat endpoint above. One entry that works on every model and returns that endpoint's native citation artifacts, so client code doesn't branch per provider. Theenginefield controls routing:auto(default; the model's native provider search where supported, else Opper's engine),native(require native, 400 otherwise),opper(always Opper's engine), or pin a backend withjina/exa. Other knobs:max_results,max_uses,search_context_size(very_low→full),allowed_domains/excluded_domains,country,freshness. Beta. - Native — a provider's own shape (
web_search_20250305,{type:"web_search"},{googleSearch:{}}) forwards verbatim to the routed provider. - Standalone —
POST /v3/tools/web/searchruns one search directly, no model and no agentic loop.
Authoritative field reference: the CanonicalWebSearchTool schema in the spec (curl -s https://api.opper.ai/v3/openapi.yaml | grep -i -n CanonicalWebSearchTool). Guide: docs.opper.ai/build/gateway/web-search.
Other v3 surfaces
The v3 spec also covers tracing (/v3/spans, /v3/traces), generations, files (/v3/files), built-in web tools, and async artifacts (/v3/artifacts/{id}/status). Fetch the spec for shapes; this skill won't enumerate them — they change.
Knowledge bases — on the v2 surface
Knowledge bases (indexes) live at /v2/knowledge/..., served from the same host with the same Bearer auth. They are not in /v3/openapi.yaml. For raw HTTP, fetch https://api.opper.ai/v2/openapi.json. For application code, the SDKs and CLI abstract this: opper.knowledge.* (Python / TS) and opper indexes ....
Migration — from another gateway, or from Opper's own /call
Moving from OpenRouter, OpenAI, Anthropic, or similar: see references/migration.md. Recap: point the base URL at https://api.opper.ai/v3/compat and use your Opper API key.
Moving off Opper's own legacy /call (POST /v3/call or the v2 Python SDK's opper.call): same file — it opens with the field-by-field /call → chat-completions mapping (name → X-Opper-Name header, instructions → system message, output_schema → response_format, data → parsed choices[0].message.content).
Coding assistant integrations
For wiring Opper into Claude Code, Cursor, Copilot, Continue, etc., see the up-to-date list at docs.opper.ai/overview/integrations.
Non-obvious gotchas
- One gateway, many endpoints. Text/chat is
/v3/compat/...; media generation, files, and realtime are a separate surface (theopper-multimodalskill). All share the same key and governance. /v3/callis legacy and being sunset. Never point a new integration at it, and don't use it in examples. Existing/callusers (including legacy v2 Python SDKopper.call) migrate to compat +response_format— see references/migration.md.- Compat endpoints live under
/v3/compat/..., not at/v3/.... SDK migrations should point base URL athttps://api.opper.ai/v3/compat. - Auth is
Authorization: Bearer ...only. Anthropic SDKs sendx-api-keyby default — set theAuthorizationheader explicitly when migrating. - Structured output is a parameter, not an endpoint — use
response_format: {type: "json_schema"}on a compat chat call. - A bare model name is a pool, not a provider.
"model": "kimi-k3"routes to any provider serving it;"tensorx/moonshotai/kimi-k3"pins one;"my-alias"uses your own ordered list;"dynamic/my-route"runs a deployed graph. The responsemodelfield just echoes what you sent, so only the trace (span.meta.model) reveals which one ran — see references/model-routing.md. - Server-side web search is portable — one
{"type": "opper:web_search"}tool entry works on every model; native provider shapes also forward verbatim. - Media / files / realtime are not in this skill — they're dedicated endpoints documented in
opper-multimodal. Don't improvise their shapes here. - Knowledge is v2, not v3. Don't reach for
/v3/knowledge/...— it does not exist. - The spec is the most up-to-date reference; the docs and this skill follow it. This file rots; the spec doesn't.
Where to look next
| For | Look at |
|---|---|
| Live, definitive endpoint shapes | https://api.opper.ai/v3/openapi.yaml |
| Concepts (Organization, Project, Call, Trace, Gateway, Control Plane) | docs.opper.ai/overview/concepts |
| Gateway behaviour (routing, compat) | docs.opper.ai/overview/gateway |
| Control Plane (Route / Observe / Steer / Guard / Comply) | docs.opper.ai/control-plane/overview |
| Media generation, files, vision/PDF input, realtime voice | the opper-multimodal skill |
| Multimodality concepts (docs) | docs.opper.ai/build/multimodal/overview |
| Roundtable | docs.opper.ai/build/roundtable/overview |
| Browsable model catalog (for user-facing recommendations) | opper.ai/models |
| Worked recipes in many languages | github.com/opper-ai/opper-cookbook |
| Provider-compatible endpoints, deeper | references/compatibility.md |
Migrating from OpenRouter / OpenAI / Anthropic / Opper's legacy /call |
references/migration.md |
| Model routing — pools, pins, org aliases, dynamic routes, failover | references/model-routing.md |
| Calling the API from Python or TypeScript | the opper-sdks skill |
| Calling the API from a terminal | the opper-cli skill |