plungeai-models
Models plane — /v1/chat/completions, /v1/embeddings, /v1/models
The OpenAI-compatible surface ("money plane"). Auth is a different key from the rest of the API: Authorization: Bearer sk-ocean-YOURKEY (from Ocean Dashboard → One API → Keys, "Model gateway keys" panel). Errors use the OpenAI error shape {"error":{"message","type","code","requestid"}}.
The OpenAI-compatible surface ("money plane"). Auth is a different key from
the rest of the API: Authorization: Bearer sk-ocean-YOUR_KEY (from Ocean
Dashboard → One API → Keys, "Model gateway keys" panel). Errors use the OpenAI error shape
{"error":{"message","type","code","request_id"}}.
Drop-in base_url swap
Point the OpenAI SDK you already use at the base URL. That is the entire integration:
Python
from openai import OpenAI
client = OpenAI(base_url="https://api.plungeai.com/v1",
api_key="sk-ocean-YOUR_KEY")
r = client.chat.completions.create(
model="anthropic/claude-sonnet-5",
messages=[{"role": "user", "content": "Hello"}])
print(r.choices[0].message.content)TypeScript
import OpenAI from 'openai'
const client = new OpenAI({
baseURL: 'https://api.plungeai.com/v1',
apiKey: process.env.PLUNGEAI_INFERENCE_KEY, // sk-ocean-YOUR_KEY
})
const r = await client.chat.completions.create({
model: 'anthropic/claude-sonnet-5',
messages: [{ role: 'user', content: 'Hello' }],
})curl
curl -s https://api.plungeai.com/v1/chat/completions \
-H "Authorization: Bearer sk-ocean-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"anthropic/claude-sonnet-5","messages":[{"role":"user","content":"Hello"}]}'{
"id": "chatcmpl-...",
"object": "chat.completion",
"model": "anthropic/claude-sonnet-5",
"choices": [{ "index": 0,
"message": { "role": "assistant", "content": "Hello! How can I help?" },
"finish_reason": "stop" }],
"usage": { "prompt_tokens": 8, "completion_tokens": 9, "total_tokens": 17 }
}Model slugs are provider/model. Get the live priced list from
GET /v1/models — never hardcode a slug list from memory. The enforcement is
404 model_not_found: an unknown slug fails, so populate model choices
from GET /v1/models at runtime.
Streaming ("stream": true → text/event-stream), any other
OpenAI-compatible field (tools, response_format, top_p, …) passes
through untouched.
POST /v1/chat/completions — request fields beyond OpenAI's
| Field | Type | Semantics |
|---|---|---|
model | string | One slug, or @preset/<slug>. Ignored if models is set |
models | string[] | Ordered fallback list — tried in order; first success serves |
sort | "price" | "latency" | "throughput" | Reorders models[] before the first attempt. Providers with no latency/throughput history sort last |
temperature | number | Exactly 0 makes the request response-cache eligible |
Routing & failover (models[])
curl -s https://api.plungeai.com/v1/chat/completions \
-H "Authorization: Bearer sk-ocean-YOUR_KEY" -H "Content-Type: application/json" \
-d '{"models":["anthropic/claude-sonnet-5","openai/gpt-5"],
"sort":"price",
"messages":[{"role":"user","content":"Hello"}]}'- Each candidate gets one retry (2 attempts total) on
429/5xx/network error; backoff honors a shortRetry-After(capped 2s) or flat 250ms. - Exhausting a candidate excludes its provider for 30 seconds and
moves to the next; a still-excluded candidate shows
"reason":"outage_excluded"in the loggedroute_attempts(not re-probed). - A non-429
4xx(your request is malformed) is not retried and not failed over — the next candidate would fail identically. - Honest billing: the response's
modelfield is the slug that actually served the request — possibly a fallback, not what you asked for first. Bill, log, and display on the responsemodel, never on the requested one. - Streaming caveat: failover works identically for
stream:true— the decision happens on the response status before any bytes are forwarded — but a stream that dies mid-flight is not retried. Themodelfield inside SSE chunks reflects the serving upstream natively; billing still uses the honest slug.
Presets — @preset/<slug>
A stored model+routing+params bundle, addressed via the model field:
curl -s https://api.plungeai.com/v1/chat/completions \
-H "Authorization: Bearer sk-ocean-YOUR_KEY" -H "Content-Type: application/json" \
-d '{"model":"@preset/fast","messages":[{"role":"user","content":"Hello"}]}'Request-explicit fields always override the preset — it only fills gaps.
Unknown/inactive slug → 404 preset_not_found. Presets are administered
platform-side; ask your admin which exist for your org.
Response cache (opt-in, per org)
- Eligible only when
temperature: 0is explicit andstreamis not true. - Cache key = hash of the full request body — any differing field misses.
- Header
x-cache: miss(stored for next time) /x-cache: hit(served from cache, never billed, same response id). Header absent entirely when the org has caching off or the request wasn't eligible. - TTL is per-org, clamped 60s–86400s.
Guardrails & pricing (BYOK-or-billed)
Per-org/per-key policy — spend caps, provider/model allow-lists, content-blocking, BYOK requirements — enforced before cache lookup and before any money moves. Strictest wins when org and key policies overlap.
Pricing: platform-billed by default (provider cost + org markup). Connect your own provider key (BYOK, Dashboard → One API → Catalog → "BYOK provider keys") and requests to that provider route through it for a small routing fee instead. Org policy can require BYOK per provider. Trial orgs are metered but never charged.
Error catalogue
| Status | Code | Meaning | Handling |
|---|---|---|---|
| 400 | invalid_json | Request body is not valid JSON | Fix the body |
| 401 | invalid_api_key | Missing/invalid sk-ocean- key | Fix the key |
| 402 | insufficient_balance | Credit balance at $0 — the first error every platform-billed org hits | Surface: top up (the message carries the URL) |
| 402 | byok_required | Provider requires your own connected key on this plan | Surface: connect key in Dashboard → One API → Catalog, or change plan |
| 403 | model_not_allowed | All routing candidates excluded by allow-list | Surface; pick an allowed model (see GET /v1/models) |
| 403 | content_blocked | Prompt matched a guardrail regex (pattern never echoed) | Surface; do not retry variants to probe the filter |
| 404 | model_not_found | Unknown model slug | Re-pick from GET /v1/models; never hardcode slugs |
| 404 | preset_not_found | @preset/<slug> unknown or inactive | Fix the slug |
| 429 | rate_limit_exceeded | Atomic per-key limiter — no Retry-After on this path | Fixed short backoff (~1s), then retry |
| 429 | insufficient_quota | Monthly spend limit reached (type rate_limit_error) — distinct from both other 429s | Surface — resets with the billing period; do NOT retry-loop |
| 429 | spend_cap_exceeded | Guardrail cap reached | Surface — a policy, not a transient; do NOT retry-loop |
| varies | upstream_error | Provider failure — the provider's own status is passed through (type api_error) | One retry with backoff (routing already retried/failed over) |
| 500 | internal_error | Gateway error | One retry with backoff |
| 503 | money_plane_unavailable | Inference gateway not bound on this tier | Surface; wrong tier/deployment |
Example 402:
{"error":{"message":"This provider requires your own API key on your plan — connect it in Settings → Integrations, or upgrade to platform-billed usage","type":"byok_required","code":"byok_required","request_id":"00000000-0000-4000-8000-000000000004"}}POST /v1/embeddings
Same auth, same guardrail enforcement as chat completions.
curl -s https://api.plungeai.com/v1/embeddings \
-H "Authorization: Bearer sk-ocean-YOUR_KEY" -H "Content-Type: application/json" \
-d '{"model":"openai/text-embedding-3-small","input":["hello world"]}'{
"object": "list",
"data": [{ "object": "embedding", "index": 0, "embedding": [0.0123, -0.0456, "..."] }],
"model": "openai/text-embedding-3-small",
"usage": { "prompt_tokens": 2, "total_tokens": 2 }
}input is a string or an array of strings.
GET /v1/models — the priced catalog
The live list routing candidates are drawn from — ids, pricing, provider.
curl -s https://api.plungeai.com/v1/models \
-H "Authorization: Bearer sk-ocean-YOUR_KEY"{
"object": "list",
"data": [
{ "id": "anthropic/claude-sonnet-5", "provider": "anthropic",
"pricing": { "input": "...", "output": "..." } },
{ "id": "openai/gpt-5", "provider": "openai", "pricing": { "...": "..." } }
]
}Generated code that lets users pick a model should populate the choice from this endpoint at runtime, not from a baked-in list.