For AI agents: a documentation index is available at https://docs.plungeai.com/llms.txt. Append .md to any page URL, or send Accept: text/markdown, to get markdown. Setup instructions for agents are at https://docs.plungeai.com/agents.md. Execution planes take an ozk_ key; the models plane takes an sk-ocean- key.

Documentation Index: fetch the complete documentation index at /llms.txt. Use this file to discover all available pages before exploring further.

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

FieldTypeSemantics
modelstringOne slug, or @preset/<slug>. Ignored if models is set
modelsstring[]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
temperaturenumberExactly 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 short Retry-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 logged route_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 model field is the slug that actually served the request — possibly a fallback, not what you asked for first. Bill, log, and display on the response model, 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. The model field 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: 0 is explicit and stream is 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

StatusCodeMeaningHandling
400invalid_jsonRequest body is not valid JSONFix the body
401invalid_api_keyMissing/invalid sk-ocean- keyFix the key
402insufficient_balanceCredit balance at $0 — the first error every platform-billed org hitsSurface: top up (the message carries the URL)
402byok_requiredProvider requires your own connected key on this planSurface: connect key in Dashboard → One API → Catalog, or change plan
403model_not_allowedAll routing candidates excluded by allow-listSurface; pick an allowed model (see GET /v1/models)
403content_blockedPrompt matched a guardrail regex (pattern never echoed)Surface; do not retry variants to probe the filter
404model_not_foundUnknown model slugRe-pick from GET /v1/models; never hardcode slugs
404preset_not_found@preset/<slug> unknown or inactiveFix the slug
429rate_limit_exceededAtomic per-key limiter — no Retry-After on this pathFixed short backoff (~1s), then retry
429insufficient_quotaMonthly spend limit reached (type rate_limit_error) — distinct from both other 429sSurface — resets with the billing period; do NOT retry-loop
429spend_cap_exceededGuardrail cap reachedSurface — a policy, not a transient; do NOT retry-loop
variesupstream_errorProvider failure — the provider's own status is passed through (type api_error)One retry with backoff (routing already retried/failed over)
500internal_errorGateway errorOne retry with backoff
503money_plane_unavailableInference gateway not bound on this tierSurface; 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.

Planned: TI-33

Search is not available yet. Until it ships, use the page index or browse the sidebar.

Planned: TI-34

The docs assistant is not available yet. You can hand these docs to your own assistant instead.