One API
OpenRouter compatibility
Point your OpenAI or OpenRouter client at https://api.plungeai.com/v1, swap the key for your ozk_… platform key, and POST /v1/chat/completions works with the same provider/model slugs and the same models[] fallback array — Ocean is an OpenAI-compatible router with OpenRouter-style multi-provider addressing. What it is not is a drop-in for OpenRouter's cost-accounting fields, its error shape, or its provider-routing request object. This chapter is the developer-facing matrix; the exhaustive live/static diff (every field probed, request ids logged) is the full comparison log at orchestration/api-gateway/live-test-reports/2026-09-18-openrouter-compat/OPENROUTER-COMPAT-LOG.md.
5.0 Drop-in mode
Point an OpenRouter client at https://api.plungeai.com/api/v1 and use your ozk_ account key: /api/v1/* is the same models plane as /v1/* — same routing, guardrails, response cache, billing and metering — in OpenRouter's wire shape. Only the shape differs:
/api/v1 (OpenRouter shape) | /v1 (OpenAI shape) | |
|---|---|---|
| Error envelope | {"error":{"code":401,"message":"…","metadata":{"error_type":"authentication","ocean_code":"invalid_api_key","ocean_type":"authentication_error","request_id":"…"}}} — numeric code, provider detail in metadata | {"error":{"message":"…","type":"authentication_error","code":"invalid_api_key","request_id":"…"}} |
Completion id | gen-…, the generation id GET /generation?id= takes; also the x-generation-id header | chatcmpl-… |
system_fingerprint | always present (null when the provider sent none) | as the provider sent it |
| Stream | : OPENROUTER PROCESSING keepalive until the first byte; the usage chunk is always sent and repeats finish_reason | usage chunk only with stream_options.include_usage |
GET /models | public; OpenRouter's item keys, pricing = USD-per-token strings, plus links and total_count | needs a key; Ocean's item shape (3.8) |
curl -s -X POST "$API_BASE/api/v1/chat/completions" -H "content-type: application/json" \
-d '{"model":"openai/gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'The drop-in base lists models without a key (live-verified 2026-10-04):
curl -s -o /dev/null -w '%{http_code}\n' "$API_BASE/api/v1/models"5.1 Migration from OpenRouter
| Concern | OpenRouter | Ocean | Change needed |
|---|---|---|---|
| Base URL | https://openrouter.ai/api/v1 | https://api.plungeai.com/v1 | change base URL |
| Key | sk-or-… | ozk_… | new key (Authentication & keys) |
| Auth header | Authorization: Bearer | Authorization: Bearer | none |
| Model slug | provider/model | provider/model | none (bare aliases also resolve, 3.7) |
| Fallback list | models: [...] | models: [...] | none — same field, same semantics (3.3) |
| App attribution | HTTP-Referer, X-Title headers | ignored (no effect, no error) | drop them or leave them — they do nothing |
| Cost in response | usage.cost, cost_details | usage.cost, cost_details, is_byok with usage:{"include":true} (3.5) | none |
| Provider routing | provider:{...} request object | not interpreted (see below) | use sort / models / presets |
5.2 Endpoints
| OpenRouter path | Ocean | Notes |
|---|---|---|
POST /chat/completions | same | field caveats in 5.3 |
GET /models | same on /api/v1 · differs on /v1 | public, OpenRouter's item shape under /api/v1; key-gated Ocean shape under /v1 (5.6) |
POST /embeddings | differs | openai, mistral, google and cohere models; no /embeddings/models list route (3.9) |
POST /completions (legacy text) | same | prompt must be one string; served on /v1 and /api/v1 (3.1) |
GET /generation (cost lookup) | same | GET /generation?id=<gen-… id> (3.15); traces stay at GET /v1/traces/:id (Traces) |
GET /credits, /key | same | (3.15); /auth/key answers too. Key management (/keys, POST /auth/keys) stays in the dashboard |
GET /models/{author}/{slug}/endpoints | same | public under /api/v1 (3.15) |
/images, /audio/* | missing | not offered |
/v1/agents, /v1/workflows, /v1/tools, /v1/mcp, /v1/discovery, /v1/traces | Ocean-only | the execution planes (5.9) |
5.3 Request fields
Full per-provider-shape table is in 3.2. Relative to OpenRouter:
| Field | OpenRouter | Ocean |
|---|---|---|
model, models, messages, stream, stream_options, temperature, top_p, max_tokens, stop, tools, tool_choice, response_format, seed, penalties, logit_bias, logprobs, top_k, min_p, top_a, reasoning, reasoning_effort | supported | openai-shape: forwarded after one field split (OpenAI itself never receives top_k, min_p, top_a; reasoning.effort is sent as reasoning_effort). On anthropic/google/cohere some are dropped in translation and named in x-ocean-dropped-params — see 3.2. |
sort | not a request field | Ocean-only — price/latency/throughput |
provider (routing object) | supported | ignored — accepted and removed before the provider; use models / sort |
route:"fallback" | supported | ignored — accepted and removed; models[] always fails over in order |
transforms, plugins | supported | ignored — accepted and removed before the provider |
prompt_cache_key, prompt_cache_options, cache_control | supported | openai-shape passthrough; anthropic honours a cache_control marker on a system text part (3.5), otherwise ignored |
usage:{include:true} | opt-in cost accounting | supported — adds usage.cost, cost_details, is_byok (3.5); Ocean meters either way |
5.4 Response fields
| Field | OpenRouter | Ocean |
|---|---|---|
id | gen-… | gen-… under /api/v1; chatcmpl-… under /v1 |
object, created, model, choices, usage | present | present (model is the honest served slug, 3.3) |
provider (top-level) | present | present on every response and stream chunk |
openrouter_metadata, system_fingerprint, service_tier | present | openrouter_metadata, service_tier absent; system_fingerprint always under /api/v1 (null when the provider sent none), as the provider sent it under /v1 |
choices[].native_finish_reason | present | present on every choice |
usage.cost, usage.cost_details, usage.is_byok | present | present with usage:{"include":true} (3.5); the non-stream price is also the x-ocean-cost-usd header |
usage.prompt_tokens_details.cached_tokens | present | present; Ocean also adds cache_write_tokens (Ocean-only) |
| error-in-choice (mid-stream) | present | matched on the agent plane (error frame, no [DONE]) |
5.5 Streaming
| Aspect | OpenRouter | Ocean |
|---|---|---|
| Wire | chat.completion.chunk + [DONE] | same (4) |
| Keepalive comment | : OPENROUTER PROCESSING | same on /api/v1 (until the first byte); none on /v1; the agents plane sends : OCEAN PROCESSING |
Final usage chunk | included (with cost) | /api/v1: always, repeating finish_reason; /v1: anthropic/google/cohere always, openai-shape only with stream_options.include_usage |
| Mid-stream error | error object, close without [DONE] | matched on the agents plane; money-plane openai passthrough relays the upstream's own bytes |
5.6 Models list
OpenRouter GET /models returns data[].{id, canonical_slug, name, created, description, context_length, architecture, pricing:{prompt,completion}, top_provider, supported_parameters, …} where pricing is USD-per-token strings. Ocean returns data[].{id, object, created, owned_by, context_length, max_output, pricing:{in,out}, capabilities:{streaming,tools,vision,embedding,fim}} where pricing.in/.out are USD-per-1M numbers (3.8). /v1/models items also carry name, canonical_slug, architecture, top_provider, supported_parameters, per_request_limits and the per-token price as pricing_per_token; only pricing itself keeps the per-1M numbers.
Under /api/v1/models the list is OpenRouter's: public (no key), data[] items carry id, canonical_slug, name, created, description, context_length, architecture, pricing (USD-per-token strings), top_provider, per_request_limits, supported_parameters, default_parameters, links, supported_voices, and the envelope adds links and total_count.
5.7 Errors
OpenRouter's envelope is {"error":{"message":"…","code":401,"metadata":{…}}} — a numeric code, an optional metadata, no type. Ocean's money-plane envelope is {"error":{"message":"…","type":"authentication_error","code":"invalid_api_key","request_id":"…"}} — a string code, a type, a request_id, no metadata. The execution planes use a third shape ({"error":{"code":"…","message":"…"}} + X-Error-Code header). Full tables: Errors, limits & headers.
curl -s "$API_BASE/v1/chat/completions" -H "content-type: application/json" \
-d '{"model":"openai/gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":8}'The "code" above is the string invalid_api_key, not the integer 401 an OpenRouter SDK expects. Under /api/v1 the same request answers "code":401 with metadata.error_type in OpenRouter's vocabulary (authentication, payment_required, rate_limit_exceeded, …) and the Ocean code in metadata.ocean_code (5.0).
5.8 What breaks an OpenRouter SDK user today
Every item below describes /v1. Under /api/v1 (5.0) the error envelope, gen- ids, keepalive, usage chunk and /models behave as OpenRouter's.
error.codeis a string, not a number — code that branches onerr.error.code === 401never matches (compareerr.status/ the HTTP status instead).- Cost fields are opt-in —
usage.cost,usage.cost_details,usage.is_byokappear withusage:{"include":true}(3.5); without it, readx-ocean-cost-usdorGET /v1/generation(3.15). - No
openrouter_metadatain the response;providerandnative_finish_reasonare present. - OpenRouter-only request fields are not honoured —
provider,route,transforms,pluginsare accepted and removed before the provider, with no effect. Replace withmodels+sort+ presets. - No
: OPENROUTER PROCESSINGkeepalive on the money-plane stream; openai-shape streams carry a finalusagechunk only if you passstream_options.include_usage. /modelsshape differs — needs a key, andpricingis per-1M numbers (the per-token strings are inpricing_per_token).HTTP-Referer/X-Titleare ignored — no app rankings / attribution.- No
/images,/audio,/responses,/messages,/providersendpoints;/generation,/credits,/key(3.15) and/completions(3.1) are served. - BYOK is dashboard-configured, not a per-request
providerobject (3.12).
5.9 Ocean-only capabilities you gain
- Execution planes an OpenRouter key cannot reach: Tools, Agents, Workflows, MCP, Discovery.
sort— route candidates byprice,latency, orthroughput(3.3).@preset/<slug>— server-stored routing + param bundles (3.4).- Guardrails — org/key spend caps, model/provider allow-lists, content filter,
require_byok(3.10). - Exact-match response cache —
$0hits with anx-cacheheader (3.11). - Traces & honest billing —
GET /v1/traces/:id, honest served-model reporting after failover (Traces).