# PlungeAI API Overview
Source: https://www.plungeai.com/docs/getting-started/overview
## Set up PlungeAI via MCP
One server, 22 tools. Needs an ozk_ key from Dashboard → One API → Keys. Works in every major coding agent.
[See all install options →](/developer-tools/mcp/quickstart)
**Run in terminal.** Run this command in your terminal. Set PLUNGE_API_KEY first; the header is stored in your Claude Code config.
```bash
claude mcp add --transport http plungeai https://mcp.plungeai.com/v1 --header "Authorization: Bearer $PLUNGE_API_KEY"
```
**Verify installation:** Run /mcp to see plungeai, then ask: use plungeai_whoami to confirm my identity
[Learn more](/integrations/claude-code)
**Run in terminal.** Run this command in your terminal. Codex reads the key from PLUNGEAI_API_KEY at run time: export PLUNGEAI_API_KEY=$PLUNGE_API_KEY.
```bash
codex mcp add plungeai --url https://mcp.plungeai.com/v1 --bearer-token-env-var PLUNGEAI_API_KEY
```
**Verify installation:** /mcp lists plungeai
[Learn more](/integrations/codex)
**One-click install.** Add PlungeAI to Cursor: [Add to Cursor →](https://mcp.plungeai.com/install/cursor)
Then add the Authorization header in ~/.cursor/mcp.json (user-level file, never a repo file).
**Verify installation:** In agent chat: use plungeai_whoami to confirm my identity
[Learn more](/integrations/cursor)
**One-click install.** Add PlungeAI to VS Code: [Add to VS Code →](https://mcp.plungeai.com/install/vscode)
Or from a terminal:
```bash
code --add-mcp "{\"name\":\"plungeai\",\"type\":\"http\",\"url\":\"https://mcp.plungeai.com/v1\",\"headers\":{\"Authorization\":\"Bearer $PLUNGE_API_KEY\"}}"
```
**Verify installation:** In Copilot agent chat: plungeai_whoami
[Learn more](/integrations/vscode)
These docs use PLUNGE_API_KEY; the PlungeAI skills, the Claude Code plugin and the install page use PLUNGEAI_API_KEY; any name works if your config and shell agree.
## Onboard your agent
Connect PlungeAI by pasting this prompt to your agent.
```text
Use curl to read https://www.plungeai.com/docs/agents.md and perform the setup to connect PlungeAI
```
The file it reads, [/agents.md](/agents.md), asks you for a key, connects your client to the MCP server, verifies the connection with `plungeai_whoami` and lists the rules the agent follows.
## Build with PlungeAI APIs
Pick a product and open its quickstart. Not sure which API to use? Try the [API chooser](/getting-started/choose-an-api).
[Create an API key](https://dashboard.plungeai.com/one-api?tab=keys)
OpenAI-compatible chat with fallback and presets. Key: `ozk_`.
A typed tool call behind a trust fence. Key: `ozk_`.
One prompt, sync, async or streamed. Key: `ozk_`.
Multi-agent CNL pipelines, inline or saved. Key: `ozk_`.
An agent loop inside allowed tools and iterations. Key: `ozk_`.
Cron agents, heartbeats and notifications. Key: `ozk_`.
Find agents, tools and models before you call. Key: `ozk_`.
# Choose an API
Source: https://www.plungeai.com/docs/getting-started/choose-an-api
## What are you building?
Find your job in the first column, answer its question, and start with the API the answer names.
| Job | Question | Answer → start with |
|---|---|---|
| **Call a model from my code**: chat or embeddings in my app | Do you need one fixed model, or automatic fallback? | One model → [Chat completions](#start-with-chat-completions) · Fallback, cheapest or fastest → [Model routing](#start-with-model-routing) |
| **Run one capability**: search, scrape, send, look up | Do you already know which tool? | Yes → [Tools](#start-with-tools) · No → [Discovery](#start-with-discovery) |
| **Get an answer from an agent**: one prompt in, an answer out | Do you need the answer now, or can it arrive later? | Now (or streamed) → [Agents (sync)](#start-with-agents-sync) · Later → [Agents (async)](#start-with-agents-async) |
| **Chain several agents**: a pipeline with fixed steps, or an open goal | Should an agent choose the steps itself, within limits? | No, fixed steps → [Workflows](#start-with-workflows) · Yes → [Missions](#start-with-missions) |
| **Run something on a schedule**: every hour, every day, on a condition | none | [Scheduling](#start-with-scheduling) |
| **Work from my coding agent**: Claude Code, Cursor, Codex, VS Code | none | [the MCP server](#start-with-the-mcp-server) |
The interactive chooser is planned (TI-74); until then this table and the results below carry the same data.
### Start with Chat completions
You call one model you already chose through the OpenAI-compatible chat API, so any OpenAI SDK works once you change the base URL.
**How to start:** base URL `https://api.plungeai.com/v1`; key `$PLUNGE_API_KEY` (your `ozk_` key); `model` set to a slug from `GET /v1/models`; `stream: true` if you want SSE chunks. [Setup details →](/models/quickstart) · [Build with Chat completions →](/models/quickstart)
| | |
|---|---|
| What you send | `POST /v1/chat/completions` with `model`, `messages` and your `ozk_` key |
| What you get back | `chat.completion` (or SSE chunks) with `usage` |
| What your app handles | Retries on 429 `rate_limit_exceeded` and 5xx; model choice |
| How long it takes | One model call; streaming starts at the first token |
| What you pay for | Tokens, billed at provider cost × your org markup ([Pricing](/getting-started/pricing)) |
| What to check | `x-request-id`, `usage`, served `model` ([Models quickstart](/models/quickstart)) |
**Before you build:** the models plane takes the same `ozk_` key as every other plane; a missing or invalid key answers `401 invalid_api_key`.
### Start with Model routing
The gateway tries your candidates in order, or sorts them by price, latency or throughput, so one failing provider does not fail the request.
**How to start:** the Chat completions setup plus `models: ["", ""]` and `sort` set to `price`, `latency` or `throughput`, or `model: "@preset/"` for a saved preset; the Python SDK passes these in `extra_body`. [Setup details →](/models/routing) · [Build with Model routing →](/models/routing)
| | |
|---|---|
| What you send | The same request, plus `models[]`, `sort` or `model: "@preset/"` |
| What you get back | The completion from the first candidate that succeeds; `model` names the one that served |
| What your app handles | Reading which model served |
| How long it takes | One call, plus failover attempts |
| What you pay for | Tokens of the model that served |
| What to check | The `model` field, and 502 "All routing candidates failed" |
**Before you build:** read the served `model` field before you log costs; it can differ from your first candidate.
### Start with Tools
You already know the capability, and one tool call gives typed params, one outcome object and the trust fence in a single request.
**How to start:** key `$PLUNGE_API_KEY` (an `ozk_` key); read `GET /v1/tools/{id}` for `operations[]` and `inputSchema`; send `{operation, params}` to `POST /v1/tools/{id}/execute`. [Setup details →](/tools/quickstart) · [Build with Tools →](/tools/quickstart)
| | |
|---|---|
| What you send | `POST /v1/tools/{id}/execute {operation, params}` with an `ozk_` key |
| What you get back | `{ok, content, outcome, execution_id, request_id}` |
| What your app handles | 422 `invalid_params`, 409 `approval_required`, 424 `connection_required` |
| How long it takes | One tool run; `mode: async` over MCP for long ones |
| What you pay for | Counts toward tier POST limits; per-call price TBD by owner (TI-31) |
| What to check | `outcome`, `execution_id`, the trace ([Outcomes](/tools/outcomes)) |
**Before you build:** search first; never hard-code a tool id. Handle 409 `approval_required` and 424 `connection_required` before you ship.
### Start with Discovery
You do not know the right tool or agent yet, and search returns ranked cards you can read before you spend a call.
**How to start:** `GET /v1/discovery/search` with `q` (your need in plain words), `kind` (for example `agents`), `mode` `hybrid` (or `keyword`, `vector`), `fields` `summary` and `limit`; page with `offset`. [Setup details →](/discovery/core-concepts/search-modes) · [Build with Discovery →](/discovery/quickstart)
| | |
|---|---|
| What you send | `GET /v1/discovery/search?q=&kind=` |
| What you get back | `{cards, count, searchMethod, query}` |
| What your app handles | Picking an id, then reading the contract |
| How long it takes | One synchronous GET |
| What you pay for | GETs are not rate-limited; price TBD by owner |
| What to check | `status:active` on the card ([Card status](/discovery/core-concepts/card-status)) |
**Before you build:** call only cards with `status: active`; search filters on `active` by default, so widen `status` only on purpose.
### Start with Agents (sync)
One prompt in and one answer out on the same connection is the shortest path to a registry agent, and `stream: true` shows tokens as they arrive.
**How to start:** key `$PLUNGE_API_KEY`; `POST /v1/agents/one-agent/execute` with `prompt`, `sync: true` (the default) and `max_tokens`; add `stream: true` for OpenAI chunks. [Setup details →](/agents/quickstart) · [Build with Agents →](/agents/quickstart)
| | |
|---|---|
| What you send | `POST /v1/agents/{id}/execute {prompt, sync: true}` (or `stream: true`) |
| What you get back | `{content, workflow_id, task_id, request_id}` or OpenAI SSE chunks |
| What your app handles | Holding the connection; keepalives |
| How long it takes | One agent run on one connection |
| What you pay for | The model tokens the agent uses, within the tier limit; price TBD by owner (TI-31) |
| What to check | `X-Execution-Id`, `content` ([Agents quickstart](/agents/quickstart)) |
**Before you build:** search first; never hard-code agent ids. `harness-agent` is not callable; loop agents run as a `type: harness` task.
### Start with Agents (async)
The answer can arrive later, so the call returns at once with a 202 and you collect the result without holding a connection open.
**How to start:** the Agents (sync) request with `sync: false`; store `workflow_id` and `task_id` from the 202; poll `GET /v1/agents/results/{workflowId}/{taskId}` until it stops answering 404 `not_ready`. [Setup details →](/agents/features/async-results) · [Build with Agents →](/agents/features/async-results)
| | |
|---|---|
| What you send | The same, with `sync: false` |
| What you get back | 202 `{workflow_id, task_id}`, then the results route |
| What your app handles | Polling `GET /v1/agents/results/{workflowId}/{taskId}`; 404 `not_ready` means keep polling |
| How long it takes | Returns immediately; the result lands later |
| What you pay for | As Agents (sync) |
| What to check | 409 `execution_failed`, 422 `empty_completion` ([Async results](/agents/features/async-results)) |
**Before you build:** treat 409 `execution_failed` and 422 `empty_completion` as final answers, not as "not ready".
### Start with Workflows
A fixed multi-step pipeline across several agents belongs in one CNL workflow, where a parallel block costs only its slowest child.
**How to start:** `POST /v1/workflows/execute` with `workflow` (a CNL YAML string), `input` and `format` (`json`, `yaml`, `markdown` or `text`); `POST /v1/workflows/execute-stream` for SSE; `POST /v1/workflows/{id}/execute` for a saved workflow. [Setup details →](/workflows/quickstart) · [Build with Workflows →](/workflows/quickstart)
| | |
|---|---|
| What you send | `POST /v1/workflows/execute {workflow, input, format}` or `execute-stream` |
| What you get back | `{success, status: completed or incomplete, workflow_id, final_task_id, content}` or SSE engine events |
| What your app handles | Parsing SSE events; the `incomplete` status |
| How long it takes | The sum of its sequential tasks; a parallel block costs its slowest child |
| What you pay for | Every model call inside the run; price TBD by owner (TI-31) |
| What to check | `status`, `final_task_id`, the trace ([Results and traces](/workflows/core-concepts/results-and-traces)) |
**Before you build:** search first; never hard-code agent ids. Read `status: incomplete` as a result to inspect, not as a success.
### Start with Missions
An agent chooses its own steps, but only inside the tools you allow and the iteration cap you set.
**How to start:** over MCP, `plungeai_run_mission` with `goal`, `allowed_tools`, `max_iterations` (1 to 50, default 8), `success_criteria` and `mode` (default `async`); over REST, a `type: harness` task with `agent: one-agent` in `POST /v1/workflows/execute`. [Setup details →](/missions/bounds) · [Build with Missions →](/missions/quickstart)
| | |
|---|---|
| What you send | `plungeai_run_mission {goal, allowed_tools, max_iterations}` or a `type: harness` task |
| What you get back | An execution id (async by default), then the result |
| What your app handles | Polling status; relaying "AWAITING USER APPROVAL" and calling `plungeai_continue`; a trust-fence "NEEDS APPROVAL" runs only from Studio |
| How long it takes | Minutes; resumable legs of about ten minutes |
| What you pay for | The model calls in each iteration; price TBD by owner (TI-31) |
| What to check | `continuation` in the status output ([Bounds](/missions/bounds)) |
**Before you build:** the mission tool has no REST endpoint yet (TI-24). Relay "AWAITING USER APPROVAL" to the user before `plungeai_continue`.
### Start with Scheduling
Recurring agents, queries, workflows and heartbeats run on a cron schedule with notifications, with no server of your own.
**How to start:** `plungeai_schedule` with `action: create`, `name`, `job_type` (`agent`, `query`, `workflow` or `heartbeat`), `target`, `schedule` (cron), `timezone` and `notify_channel`; then `action: get` and `action: runs`. [Setup details →](/scheduling/quickstart) · [Build with Scheduling →](/scheduling/quickstart)
| | |
|---|---|
| What you send | `plungeai_schedule {action: create, job_type, target, schedule}` |
| What you get back | `job_id`; runs on the cron schedule |
| What your app handles | Verifying with `get` and `runs`; notification channel ids |
| How long it takes | Runs on the schedule |
| What you pay for | Each run, priced like the job type it runs (TI-31) |
| What to check | Run history ([Runs](/scheduling/runs)) |
**Before you build:** MCP only today (TI-24). Verify every job you create with `get` and `runs`.
### Start with the MCP server
Your coding agent gets all 20 `plungeai_*` tools from one server, so you write no client code.
**How to start:** URL `https://mcp.plungeai.com/v1`; header `Authorization: Bearer $PLUNGE_API_KEY` (an `ozk_` key); transport Streamable HTTP; one command or deeplink per client. [Setup details →](/developer-tools/mcp/quickstart#client-installation) · [Build with the MCP server →](/developer-tools/mcp/plungeai-mcp)
| | |
|---|---|
| What you send | One install command or deeplink ([Overview](/getting-started/overview)) |
| What you get back | 20 `plungeai_*` tools, 5 resources, 1 prompt in your client |
| What your app handles | Nothing: the client calls the tools |
| How long it takes | One install step, then the client is ready |
| What you pay for | Whatever the tools run |
| What to check | `plungeai_whoami` ([MCP quickstart](/developer-tools/mcp/quickstart)) |
**Before you build:** keep the key in your user-level client config or shell, never in a repo file; call `plungeai_whoami` to confirm the connection.
## Have your agent choose an API
The `choose-your-plungeai-door` skill teaches your agent the same choice.
Download [choose-your-plungeai-door.zip](https://skills.plungeai.com/choose-your-plungeai-door.zip) and add it as a skill.
Download [choose-your-plungeai-door.zip](https://skills.plungeai.com/choose-your-plungeai-door.zip) and add it with your Codex skill installer.
The skill as a docs page.
Source repository: TBD by owner (TI-12).
## Go straight to a quickstart
- [Call any model](/models/quickstart)
- [Run a tool](/tools/quickstart)
- [Run an agent](/agents/quickstart)
- [Run a workflow](/workflows/quickstart)
- [Run a bounded mission](/missions/quickstart)
- [Schedule a job](/scheduling/quickstart)
- [Discover capabilities](/discovery/quickstart)
Nothing you choose here is saved.
# PlungeAI API Pricing
Source: https://www.plungeai.com/docs/getting-started/pricing
PlungeAI bills model calls from your prepaid wallet, per token, at one of three prices. The numbers below come from `GET /v1/pricing` when these docs are published; the [live table](https://plungeai.com/pricing/) updates over a WebSocket the moment a price changes.
## How billing works
- **Wallet, reserve, settle.** Before the upstream call the gateway holds a conservative maximum from your wallet; after it, the hold settles at the true price and the rest is released. A failed call releases the hold: no charge. A near-empty wallet answers `402 insufficient_balance`.
- **What you were charged** is in the response: `usage.cost` and the `x-ocean-cost-usd` header carry the price you pay (never the provider cost). `GET /v1/generation?id=…` returns it afterwards, with `pricing_mode`.
- **Trial and active accounts.** Every request is metered; only `active` accounts are debited. A `trial` account is priced and logged but never debited, within a $10 a month platform-cost allowance (then `429 insufficient_quota`). An `active` key over its monthly limit answers `429 insufficient_quota`.
## The three prices
| Mode | What you pay | `x-ocean-pricing` |
|---|---|---|
| Posted | Ocean's published price for the model, the same whichever host serves it | `posted` |
| Passthrough | The provider cost times your organization markup | `passthrough` |
| BYOK | Your own provider key pays the provider; you pay a flat routing fee | `byok` |
Every completion carries `x-ocean-pricing` and `x-ocean-provider` (the host that served it). A posted model routes to the cheapest host that holds a key; the price does not change with the host.
**The promise:** a posted price never exceeds the model maker's own API list price.
**Price changes** reach your bill within 120 seconds of being published. **Aborted streams** settle the tokens that streamed, at the posted price.
## Posted prices
Per 1M tokens, USD, published 2026-10-29 01:00 UTC (`published_at` 1793235642982). List = the model maker's own API price. [See all models, live](https://plungeai.com/pricing/).
| Model | List in / out | Ocean in / out | Off |
|---|---|---|---|
| meta-llama/llama-3.3-70b-instruct | $1.04 / $1.04 | $0.125 / $0.40 | 75 % off |
| openai/gpt-oss-120b | $0.15 / $0.60 | $0.0463 / $0.2125 | 65 % off |
| deepseek/deepseek-v4.1-flash | $0.30 / $1.20 | $0.25 / $0.75 | 33 % off |
Every model, including passthrough ones: [Model Catalog](/models/catalog).
# API Rate Limits
Source: https://www.plungeai.com/docs/getting-started/rate-limits
Every key has a tier, copied onto the key when it is created. The tier sets per-minute and per-day windows on the execution planes and the MCP server; the models plane has one per-key limit for every tier.
| Surface | Counted on | free | pro | enterprise |
|---|---|---|---|---|
| Execution planes on `https://api.plungeai.com` (tools, agents, workflows, discovery, traces) | `POST` requests only; catalog `GET`s are not counted; a per-key override is ignored | 30/min · 1,000/day | 100/min · 10,000/day | 300/min · 100,000/day |
| MCP server `https://mcp.plungeai.com/v1` | `tools/call` only; a per-key `rate_limit` override set by PlungeAI is honoured | 30/min · 1,000/day | 100/min · 10,000/day | 300/min · 100,000/day |
| Models plane (`/v1/chat/completions`, `/v1/embeddings`, `/v1/models`) | Every request, per key | 600 req/min | 600 req/min | 600 req/min |
| Failed authentication | Per client IP, any tier | 20/min · 500/day | n/a | n/a |
A key with no tier counts as free. When you hit a limit you get 429 with Retry-After (seconds): `rate_limited` on the execution planes, `rate_limit_exceeded` on the models plane, and JSON-RPC -32000 with `error.data.retryAfter` on MCP. There are no X-RateLimit-* headers; call `plungeai_whoami` to see your live window.
## Pricing
Rate limits are separate from billing: `insufficient_quota`, `spend_cap_exceeded` and `insufficient_balance` come from billing caps, not from these windows. See [Pricing](/getting-started/pricing).
## Need higher limits?
Contact PlungeAI support and quote your `x-request-id`. Support contact: TBD by owner (TI-62).
# Your first calls
Source: https://www.plungeai.com/docs/getting-started/quickstart
Four steps from no account to a working agent call, model call and MCP connection. Every command reads its key from an environment variable; no example contains a key.
## 1. Get a key
Sign in at https://dashboard.plungeai.com and open **Dashboard → One API → Keys**. Name the key and pick an expiry: never, or 7, 30, 90, 180 or 365 days.
The key starts with `ozk_` and is shown exactly once. The same key works for steps 2 to 4.
```bash
export PLUNGE_API_KEY=ozk_... # every plane and MCP
```
Which key opens which route is on [Authentication & keys](/getting-started/authentication#the-one-key).
## 2. Call an agent
One prompt to `one-agent`, the platform's agent runtime, answered on the same connection:
```python Python
import os, requests
r = requests.post(
"https://api.plungeai.com/v1/agents/one-agent/execute",
headers={"Authorization": f"Bearer {os.environ['PLUNGE_API_KEY']}"},
json={"prompt": "In one sentence: what is a content delivery network?", "sync": True, "max_tokens": 60},
)
print(r.json()["content"])
```
```typescript TypeScript
const r = await fetch('https://api.plungeai.com/v1/agents/one-agent/execute', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.PLUNGE_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt: 'In one sentence: what is a content delivery network?', sync: true, max_tokens: 60 }),
})
console.log((await r.json()).content)
```
```java Java
var client = java.net.http.HttpClient.newHttpClient();
var request = java.net.http.HttpRequest.newBuilder(java.net.URI.create("https://api.plungeai.com/v1/agents/one-agent/execute"))
.header("Authorization", "Bearer " + System.getenv("PLUNGE_API_KEY"))
.header("Content-Type", "application/json")
.POST(java.net.http.HttpRequest.BodyPublishers.ofString("{\"prompt\":\"In one sentence: what is a content delivery network?\",\"sync\":true,\"max_tokens\":60}"))
.build();
System.out.println(client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString()).body());
```
```bash cURL
curl -s -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"prompt":"In one sentence: what is a content delivery network?","sync":true,"max_tokens":60}'
```
The answer is in `content`. Add `"stream": true` to get OpenAI-shaped chunks that end with `data: [DONE]`.
## 3. Call a model
The models plane is OpenAI-compatible: point the OpenAI SDK at `https://api.plungeai.com/v1` and pass a `provider/model` slug from `GET /v1/models`.
```python Python
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ["PLUNGE_API_KEY"], base_url="https://api.plungeai.com/v1")
resp = client.chat.completions.create(
model="anthropic/claude-sonnet-4-6",
messages=[{"role": "user", "content": "Reply with exactly the word: pong"}],
max_tokens=16,
)
print(resp.choices[0].message.content)
```
```typescript TypeScript
import OpenAI from 'openai'
const client = new OpenAI({ apiKey: process.env.PLUNGE_API_KEY, baseURL: 'https://api.plungeai.com/v1' })
const resp = await client.chat.completions.create({
model: 'anthropic/claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Reply with exactly the word: pong' }],
max_tokens: 16,
})
console.log(resp.choices[0].message.content)
```
```java Java
var client = java.net.http.HttpClient.newHttpClient();
var request = java.net.http.HttpRequest.newBuilder(java.net.URI.create("https://api.plungeai.com/v1/chat/completions"))
.header("Authorization", "Bearer " + System.getenv("PLUNGE_API_KEY"))
.header("Content-Type", "application/json")
.POST(java.net.http.HttpRequest.BodyPublishers.ofString("{\"model\":\"anthropic/claude-sonnet-4-6\",\"messages\":[{\"role\":\"user\",\"content\":\"Reply with exactly the word: pong\"}],\"max_tokens\":16}"))
.build();
System.out.println(client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString()).body());
```
```bash cURL
curl -s https://api.plungeai.com/v1/chat/completions \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"model":"anthropic/claude-sonnet-4-6","messages":[{"role":"user","content":"Reply with exactly the word: pong"}],"max_tokens":16}'
```
The `model` field of the response names the model that served the request; after a failover it can differ from the one you asked for. The cURL call is re-run live by the One API guide's example verifier; the SDK snippets send the same request.
## 4. Connect your coding agent
One command connects Claude Code to the MCP server and its 20 `plungeai_*` tools:
```bash
claude mcp add --transport http plungeai https://mcp.plungeai.com/v1 \
--header "Authorization: Bearer $PLUNGE_API_KEY" --scope user
```
`--scope user` keeps the entry in `~/.claude.json`, outside your repository; never use `--scope project` with a literal key. Run `/mcp` in a session to see `plungeai`, then ask: use plungeai_whoami to confirm my identity. Every other client is on the [MCP quickstart](/developer-tools/mcp/quickstart#client-installation).
## Request ids and traces
Every response carries a server-minted `x-request-id`; quote it when you report a problem. To group several calls into one trace, send your own UUID in `x-trace-id` and read the trace with `GET /v1/traces/{id}`:
```bash
curl -s "https://api.plungeai.com/v1/traces/$(uuidgen)" -H "Authorization: Bearer $PLUNGE_API_KEY"
```
A trace id you already used on an execution returns `409 duplicate_execution_id`: send a fresh UUID each time.
## Next steps
The seven planes and how a request flows.
Match your job to a plane.
Every field of the agent call.
### Make it a bot
A bot is a saved workflow that runs a One Agent loop on a clock, inside a policy you set. Four calls take it from nothing to a scheduled run; each reads the key from the same variable.
**1. Create it.** `kind: bot` runs the bot checks on the YAML before anything is stored:
```bash
curl -s -X POST https://api.plungeai.com/v1/workflows \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"name":"Daily Brief","kind":"bot","yaml":"workflow:\n name: Daily Brief\n tasks:\n - type: harness\n id: brief\n agent: one-agent\n goal: \"{input}\"\n max_iterations: 6\n allowed_tools: [web_search, web_fetch, task_complete]\n mission: |\n You are Daily Brief. Answer in at most five lines and cite each source URL.\n When your task is finished, call the task_complete tool with your final result.\n"}'
```
The answer is `201` with the saved row under `workflow`. Its `id` is the `` below.
**2. Set what it may do and spend.** Ask before it sends anything, never let it pay, cap one run at 25 cents:
```bash
curl -s -X PUT https://api.plungeai.com/v1/workflows//permissions \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"classes":{"send":"ask","pay":"hand_off"},"budget_usd_run":0.25}'
```
**3. Run it.**
```bash
curl -s -X POST https://api.plungeai.com/v1/workflows//execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"input":"In one line: what is the current Node.js LTS major version? Cite the URL."}'
```
The answer is in `content`. A run that stops on a held action reads `needs_approval`; answer it with `POST /v1/executions/{id}/continue`.
**4. Schedule it.** Weekdays at 09:00 Pacific:
```bash
curl -s -X POST https://api.plungeai.com/v1/schedules \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"name":"Daily Brief","workflow_id":"","schedule":"0 9 * * 1-5","timezone":"America/Los_Angeles","parameters":{"input":"Top story about Cloudflare Workers today"}}'
```
Add `"deliver": ["slack"]` under `parameters` to send each result to your Slack. The rest of the bot surface (channels, groups, triggers, files, the playbook of keys) starts at [Bots](/bots/overview).
# Authentication & keys
Source: https://www.plungeai.com/docs/getting-started/authentication
Every route except `GET /health` and `GET /v1/openapi.json` needs a key. One `ozk_` key opens every plane; a legacy `sk-ocean-` or `sk-conn-` key opens only its own plane, and a legacy key sent to an execution plane is a `401`, never a silent fallback.
These docs use PLUNGE_API_KEY; the PlungeAI skills, the Claude Code plugin and the install page use PLUNGEAI_API_KEY; any name works if your config and shell agree.
## The one key
| Prefix | Opens | Variable in these docs |
|---|---|---|
| `ozk_` | Every plane on `https://api.plungeai.com` (models, tools, agents, workflows, MCP, discovery, traces, connector proxy) and the MCP server `https://mcp.plungeai.com/v1` | `$PLUNGE_API_KEY` |
Your key is the **Ocean account key**. Billing follows the account: every metered call on any of your `ozk_` keys (models, embeddings, the connector proxy) is charged to your account's one wallet.
The older `sk-ocean-` (models plane) and `sk-conn-` (connector proxy) keys are legacy: still accepted on their own plane while you migrate, no longer created in the Dashboard.
## Which routes accept which key
The execution planes (`/v1/discovery`, `/v1/tools`, `/v1/agents`, `/v1/workflows`, `/v1/mcp`, `/v1/traces`) accept only a bearer that starts with `ozk_`; the models plane and the connector proxy accept the same `ozk_` key. The one mistake that answers `401` is a legacy `sk-ocean-` key on an execution plane. Your key on the models plane answers `200`:
```bash
# your ozk_ key on the models plane → 200
curl -s -o /dev/null -w '%{http_code}\n' https://api.plungeai.com/v1/models \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
The `401` body is the standard error envelope, and `X-Error-Code: unauthorized` carries the same code as a header.
## Header forms
| Header | Accepted on |
|---|---|
| `Authorization: Bearer ` | Every plane and the MCP server |
| `X-API-Key: ` | The execution planes and the MCP server (`ozk_` keys); **not** the models plane (`/v1/chat/completions`, `/v1/embeddings`, `/v1/models`), where it answers `401` |
```bash Authorization
curl -s "https://api.plungeai.com/v1/tools?limit=1" -H "Authorization: Bearer $PLUNGE_API_KEY"
```
```bash X-API-Key
curl -s "https://api.plungeai.com/v1/tools?limit=1" -H "X-API-Key: $PLUNGE_API_KEY"
```
## How to obtain each key
The key is self-service at **Dashboard → One API → Keys** (https://dashboard.plungeai.com/one-api?tab=keys); legacy `sk-ocean-` and `sk-conn-` keys are listed there read-only, with revoke. Name the key, pick an expiry (never, or 7, 30, 90, 180 or 365 days) and copy it: it is shown exactly once and stored hashed. A new key takes your account's tier.
The tab also shows your **Ocean account key** (prefix and last four characters, with **Rotate**): the key the Playground, `ocean-cli` and OceanCode use. The command-line clients fetch it through a browser sign-in, with nothing to paste: `ocean-cli login` and `oceancode login --provider ocean` open `https://dashboard.plungeai.com/api/cli/authorize?port=&state=`; after you sign in, the Dashboard sends a one-time code (valid 120 seconds) to the client listening on `127.0.0.1:`, and the client exchanges it with `POST https://dashboard.plungeai.com/api/cli/token` for the key.
## Expiry, revocation and rotation
- **Expiry** is checked on every request; an expired key answers `401` from that moment.
- **Revoke** a key from the same Keys list. Keys are cached briefly, so a revoked key can keep working for about two minutes before it answers `401`. If a key leaked, revoke it first and allow for that window.
- **Rotate** without downtime: create the new key, update every client, confirm it with `plungeai_whoami`, then revoke the old key.
- A lost key cannot be recovered, because only its hash is stored: create a replacement and revoke the old one.
## Key fences
Key fences are set by PlungeAI on request. Keys you create in the Dashboard carry no fence: every tool, from any address. On `https://mcp.plungeai.com/v1`, a key fenced to some tools does not see the others in `tools/list`, and a call to one answers JSON-RPC `-32602`; a key fenced to some addresses answers `401` from anywhere else.
## Checking yourself: plungeai_whoami
Ask your MCP client to "use plungeai_whoami", or call it directly. It returns the user, the auth type and tier, the key label and the current rate-limit window:
```bash
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_whoami","arguments":{"user_request":"who am I?"}}}'
```
The fields are on [Who am I](/account-api/identity/whoami).
## CORS
The One API answers browser requests from any origin, and the models plane exposes `x-request-id`. Browser code works, but a key in client-side code is a key anyone can read: call PlungeAI from your server.
## Keeping the key safe
Treat the key like a password: whoever holds it acts as your account. If a key may have leaked, rotate it now.
- Keep the literal key in a user-level client config or an environment variable. A config file inside a repository only references a variable.
- Before the first commit in a repository you configured, both commands must print nothing:
```bash
git grep -n "ozk_"
grep -rn "ozk_" . --exclude-dir=.git --exclude-dir=node_modules
```
- Never put a key in browser code or a client bundle.
# How PlungeAI works
Source: https://www.plungeai.com/docs/getting-started/concepts
The One API is one HTTPS front door, `https://api.plungeai.com`, with one router behind it. The MCP server, `https://mcp.plungeai.com/v1`, gives coding agents the same capabilities as tools.
## The seven planes
| Plane | What it does | Key routes | Key |
|---|---|---|---|
| Models | OpenAI-compatible chat, embeddings and model catalog, with ordered fallback, `sort`, `@preset/`, guardrails and an opt-in response cache | `POST /v1/chat/completions` · `POST /v1/embeddings` · `GET /v1/models` | `ozk_` |
| Tools | List tool agents, read a tool's invocation contract, execute one operation behind a trust fence | `GET /v1/tools` · `GET /v1/tools/{id}` · `POST /v1/tools/{id}/execute` | `ozk_` |
| Agents | Execute a registry agent from a prompt: sync, async pointer, or OpenAI-shaped SSE | `GET /v1/agents` · `POST /v1/agents/{id}/execute` · `GET /v1/agents/results/{workflowId}/{taskId}` | `ozk_` |
| Workflows | Run a multi-agent CNL workflow, inline or saved, with optional SSE | `POST /v1/workflows/execute` · `POST /v1/workflows/execute-stream` · `POST /v1/workflows/{id}/execute` | `ozk_` |
| MCP | Outbound MCP runs against catalog servers; coding agents connect to the MCP server | `GET /v1/mcp/tools` · `POST /v1/mcp/runs` | `ozk_` |
| Discovery | Search and recommend across the capability registry | `GET /v1/discovery/search` · `POST /v1/discovery/recommend` · `GET /v1/discovery/cards/{type}/{id}` | `ozk_` |
| Traces | Read the persisted execution trace for one trace id | `GET /v1/traces/{id}` | `ozk_` |
The machine-readable route list is `GET /v1/openapi.json`; this site serves the same operations at [/openapi.json](/openapi.json).
## Positioning
Think of the One API as an OpenRouter-compatible models plane plus everything a model router cannot do. The models plane speaks the OpenAI wire format, so an OpenAI or OpenRouter integration moves over with a base-URL swap. The execution planes are the part a router has no answer for: real tools with contracts and a trust fence, registry agents, multi-agent workflows, discovery, MCP, and a persisted trace for every run.
## The MCP endpoint
The MCP server is `https://mcp.plungeai.com/v1`: Streamable HTTP, JSON-RPC over `POST`, 20 `plungeai_*` tools, one prompt and five resources plus three resource templates. The pre-2026-09-20 path `/mcp` on the same host still answers with the same handler as a legacy alias; configure `/v1`. Details: [MCP Reference](/mcp-reference/overview).
## How a request flows
An execution-plane call runs router → CNL engine → agent:
```mermaid
flowchart LR
client[Your code] -->|ozk_ key| router[One API router]
router -->|service binding| engine[CNL engine]
engine --> agent[Registry agent]
agent --> memory[(Result and trace)]
```
1. The router checks the `ozk_` key, mints the `x-request-id`, and adopts your UUID `x-trace-id` or generates a trace id.
2. The CNL engine loads the agent card from the registry and refuses any agent that is not `status:active` before it runs.
3. The agent's result is stored and returned, and the whole run is persisted as trace spans.
Every call gets the same outcome vocabulary (`ok`, `needs_input`, `needs_connection`, `needs_api_key`, `needs_approval`, `unavailable`, `error`) and the same trust fence, whether it comes from a workflow, Ocean Studio or your code.
## Tool families
| Family | MCP tools |
|---|---|
| Discover | `plungeai_list_agents`, `plungeai_get_tool_contract`, `plungeai_templates` (list, get) |
| Run a tool | `plungeai_execute_tool` |
| Run an agent | `plungeai_execute_agent`, `plungeai_run_mission` |
| Workflows | `plungeai_list_workflows`, `plungeai_execute_workflow`, `plungeai_workflow`, `plungeai_build_workflow`, `plungeai_templates` (use) |
| Runs and results | `plungeai_get_result`, `plungeai_get_workflow_status`, `plungeai_executions`, `plungeai_followup`, `plungeai_continue` |
| Chat, memory, skills | `plungeai_chat`, `plungeai_memory`, `plungeai_learn` |
| Scheduling | `plungeai_schedule` |
| Identity | `plungeai_whoami` |
A registry agent is a platform building block in the catalog; "your workflows" are the CNL workflows you saved.
## Live-verified examples
Execution-plane examples on this site come from the One API and MCP guides, whose read-only examples were run against the deployed API and are re-run by the guides' example verifier. Models-plane examples use the same key and the same verifier where the guide marks them; the rest are verified against code. Examples that run a model or spend credits say so in the text.
## Where to get help
Service health.
Error codes and what to do.
llms.txt, markdown pages and /agents.md.
Every response carries an `x-request-id`: include it when you report a problem. Support contact: TBD by owner (TI-62).
# Models Plane Quickstart
Source: https://www.plungeai.com/docs/models/quickstart
Planned: not available yet. Tracked as TI-74.
## What this will do
OpenAI-compatible chat completions with one `ozk_` key and a base-URL swap. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Models Best Practices
Source: https://www.plungeai.com/docs/models/best-practices
Planned: not available yet. Tracked as TI-74.
## What this will do
Request fields, tools and structured output, and what each provider supports. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Evaluating Models
Source: https://www.plungeai.com/docs/models/evaluating-models
Planned: not available yet. Tracked as TI-77.
## What this will do
A guide to comparing models and agents on your own gold set: run the same prompts through several models, score the answers, and read cost and latency next to quality. No evaluation guide exists yet.
## Use this today
Send one request across an ordered list of models, sorted by price, latency or throughput.
Every model with its provider cost basis.
# Routing: models, sort, failover
Source: https://www.plungeai.com/docs/models/routing
Planned: not available yet. Tracked as TI-74.
## What this will do
Ordered fallback with `models[]`, `sort` by price, latency or throughput, and presets. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Migrate to PlungeAI
Source: https://www.plungeai.com/docs/models/migrate
Planned: not available yet. Tracked as TI-74.
## What this will do
Move from OpenRouter, the OpenAI SDK, LiteLLM or the Vercel AI SDK. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Anthropic Messages API
Source: https://www.plungeai.com/docs/models/anthropic-messages
Partly available: the route is built, and this page is read from the gateway code; it has not yet been re-run against the deployed API. Tracked as TI-78.
`POST /v1/messages` speaks the Anthropic Messages API. Change the base URL and the key of an Anthropic client and keep your request and response code. Behind it is the same path as [chat completions](/api-reference/models/create-chat-completion): `models` and `sort` failover, presets, guardrails, and billing to your wallet.
## Drop-in for the Anthropic SDK
The SDK adds `/v1/messages` to the base URL and sends your key as `x-api-key`. `Authorization: Bearer` works too. `anthropic-version` is accepted with any value.
```python Python
import os
import anthropic
client = anthropic.Anthropic(base_url='https://api.plungeai.com', api_key=os.environ['PLUNGE_API_KEY'])
message = client.messages.create(
model='anthropic/claude-haiku-4-5',
max_tokens=64,
messages=[{'role': 'user', 'content': 'hi'}],
)
print(message.content[0].text)
```
```typescript TypeScript
import Anthropic from '@anthropic-ai/sdk'
const client = new Anthropic({ baseURL: 'https://api.plungeai.com', apiKey: process.env.PLUNGE_API_KEY })
const message = await client.messages.create({
model: 'anthropic/claude-haiku-4-5',
max_tokens: 64,
messages: [{ role: 'user', content: 'hi' }],
})
console.log(message.content)
```
```bash cURL
curl -s https://api.plungeai.com/v1/messages \
-H "x-api-key: $PLUNGE_API_KEY" \
-H 'anthropic-version: 2023-06-01' \
-H 'Content-Type: application/json' \
-d '{"model":"anthropic/claude-haiku-4-5","max_tokens":64,"messages":[{"role":"user","content":"hi"}]}'
```
The same route is served at `https://api.plungeai.com/api/v1/messages`, the OpenRouter-compatible base. There `fallbacks` takes up to 3 models to try after `model`.
## Anthropic models: your body, unchanged
On an `anthropic/*` model the gateway forwards your request body as you sent it and sets only two fields: `model` (the upstream id) and `max_tokens` (the capped value). `cache_control`, `thinking`, documents, `system` blocks and server tools reach Anthropic unchanged. Ocean's own fields (`models`, `fallbacks`, `sort`, `max_cost`) are removed first.
## Every other model: translated
Any other slug, such as `openai/gpt-4o-mini`, gets the chat-completions shape:
| Messages API | Sent as |
|---|---|
| `system` | a system message |
| `tool_use` blocks | `tool_calls` on the assistant message |
| `tool_result` blocks | `tool` messages |
| `tools[].input_schema` | `function.parameters` |
| `tool_choice` `auto` / `any` / `none` / `tool` | `auto` / `required` / `none` / that function |
| `disable_parallel_tool_use` | `parallel_tool_calls: false` |
| `stop_sequences` | `stop` |
| `metadata.user_id` | `user` |
| `thinking.budget_tokens` | `reasoning_effort`: up to 35% of `max_tokens` is `low`, up to 65% `medium`, above that `high` |
Blocks with no chat-completions form (thinking, server tools) are not sent to these models.
## Responses
You get an Anthropic `message` for every model, built from the completion that served the request. `content` holds `text` and `tool_use` blocks. `model` is the slug that actually answered, which can differ from the one you asked for after a failover. `usage.input_tokens` excludes cache reads and cache writes; those are in `cache_read_input_tokens` and `cache_creation_input_tokens`. The `x-request-id` and `x-ocean-*` response headers are kept.
## Streaming
With `stream: true` the events come in Anthropic's order:
1. `message_start`
2. For each content block: `content_block_start`, one or more `content_block_delta` (`text_delta` for text, `input_json_delta` for tool input), `content_block_stop`
3. `message_delta` with `stop_reason` and the final `usage`
4. `message_stop`
`ping` events are keepalives. A failure after the response has started ends the stream with an `error` event. The SDK helpers (`client.messages.stream`) read this stream as they read Anthropic's.
## Errors
Errors use Anthropic's envelope on both `/v1/messages` and `/api/v1/messages`:
```json
{"type":"error","error":{"type":"authentication_error","message":"Invalid API key"},"request_id":"00000000-0000-4000-8000-000000000004"}
```
`error.type` follows the status: `400` `invalid_request_error`, `401` `authentication_error`, `402` `billing_error`, `403` `permission_error`, `404` `not_found_error`, `429` `rate_limit_error`, `504` `timeout_error`, any other `api_error`.
## Not supported
- The `anthropic-beta` header is not forwarded, so features behind a beta flag do not switch on.
- Responses carry `text` and `tool_use` blocks only; `stop_sequence` is always `null`.
- `/v1/messages/count_tokens` and Message Batches are not served.
## Reference
Every request field, the response schema, error codes and code samples.
# Advanced Models Settings
Source: https://www.plungeai.com/docs/models/advanced-settings
Planned: not available yet. Tracked as TI-74.
## What this will do
Guardrails, the opt-in response cache and BYOK-or-billed routing. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Upgrade from guide 2.0 to 3.0
Source: https://www.plungeai.com/docs/models/migration-guide
Planned: not available yet. Tracked as TI-74.
## What this will do
What changed between the 2.0 and 3.0 developer guides, for every plane. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Embeddings Quickstart
Source: https://www.plungeai.com/docs/models/embeddings
Planned: not available yet. Tracked as TI-74.
## What this will do
OpenAI-compatible embeddings (`openai/*` models only). This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Models, providers and slugs
Source: https://www.plungeai.com/docs/models/catalog
Every model the gateway routes, from `GET /v1/pricing` (public, no key) when these docs are published. `GET /v1/models` (with your key) returns the same models in the OpenAI shape, with `pricing.list`, `pricing.ocean` and `pricing.discount_pct`. Live prices: [plungeai.com/pricing](https://plungeai.com/pricing/).
## Model ids
A model id is the canonical `author/model` spelling, the same as OpenRouter's (for example `openai/gpt-5`). Send that id as `model`: a posted model then routes to the cheapest host holding a key and bills at the posted price.
To pin one host, send that host's upstream slug instead of the canonical id. The request then goes only to that host and bills as passthrough (`x-ocean-pricing: passthrough`).
## All models
Per 1M tokens, USD, published 2026-10-29 01:00 UTC (`published_at` 1793235642982). Ocean = the posted price; passthrough models bill at provider cost times your markup.
| Model | Provider | Context | List in / out | Ocean in / out | Off | Capabilities |
|---|---|---|---|---|---|---|
| anthropic/claude-3-5-haiku | anthropic | 200,000 | $0.80 / $4.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-3-5-sonnet | anthropic | 200,000 | $3.00 / $15.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-3-haiku | anthropic | 200,000 | $0.25 / $1.25 | passthrough | — | streaming, tools, vision |
| anthropic/claude-fable-5 | anthropic | 1,000,000 | $10.00 / $50.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-fable-5-1 | anthropic | 1,000,000 | $10.00 / $50.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-fable-5.1 | anthropic | 1,000,000 | $10.00 / $50.00 | passthrough | — | streaming, tools |
| anthropic/claude-haiku-4-5 | anthropic | 200,000 | $1.00 / $5.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-haiku-4-5-20251001 | anthropic | 200,000 | $1.00 / $5.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-haiku-5-5 | anthropic | 1,000,000 | $0.10 / $0.50 | passthrough | — | streaming, tools, vision |
| anthropic/claude-haiku-5.5 | anthropic | 1,000,000 | $0.100000 / $0.50 | passthrough | — | streaming, tools |
| anthropic/claude-opus-4 | anthropic | 200,000 | $15.00 / $75.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-opus-4-5-20251101 | anthropic | 200,000 | $5.00 / $25.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-opus-4-6 | anthropic | 1,000,000 | $5.00 / $25.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-opus-4-7 | anthropic | 1,000,000 | $5.00 / $25.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-opus-4-8 | anthropic | 1,000,000 | $5.00 / $25.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-opus-4.1 | anthropic | 200,000 | $15.00 / $75.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-opus-4.5 | anthropic | 200,000 | $5.00 / $25.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-opus-4.6 | anthropic | 1,000,000 | $5.00 / $25.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-opus-4.7-fast | anthropic | 1,000,000 | $30.00 / $150.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-opus-4.8-fast | anthropic | 1,000,000 | $10.00 / $50.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-opus-5 | anthropic | 1,000,000 | $5.00 / $25.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-opus-5-5 | anthropic | 1,000,000 | $4.00 / $20.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-opus-5-fast | anthropic | 1,000,000 | $10.00 / $50.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-opus-5.5 | anthropic | 1,000,000 | $4.00 / $20.00 | passthrough | — | streaming, tools |
| anthropic/claude-sonnet-4 | anthropic | 1,000,000 | $3.00 / $15.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-sonnet-4-5 | anthropic | 200,000 | $3.00 / $15.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-sonnet-4-5-20250929 | anthropic | 1,000,000 | $3.00 / $15.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-sonnet-4-6 | anthropic | 1,000,000 | $3.00 / $15.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-sonnet-5 | anthropic | 1,000,000 | $2.00 / $10.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-sonnet-5-5 | anthropic | 1,000,000 | $2.00 / $10.00 | passthrough | — | streaming, tools, vision |
| anthropic/claude-sonnet-5.5 | anthropic | 1,000,000 | $2.00 / $10.00 | passthrough | — | streaming, tools |
| cerebras/gpt-oss-120b | cerebras | 131,072 | $0.35 / $0.75 | passthrough | — | streaming, tools |
| cerebras/llama-3.3-70b | cerebras | 128,000 | $0.85 / $1.20 | passthrough | — | streaming, tools |
| cerebras/qwen-3.8-27b | cerebras | 131,072 | $0.990000 / $1.49 | passthrough | — | streaming, tools, vision |
| cohere/c4ai-aya-expanse-32b | cohere | 128,000 | $0.50 / $1.50 | passthrough | — | streaming |
| cohere/c4ai-aya-vision-32b | cohere | 16,000 | $0.50 / $1.50 | passthrough | — | streaming, vision |
| cohere/command-a | cohere | 256,000 | $2.50 / $10.00 | passthrough | — | streaming, tools |
| cohere/command-a-03-2025 | cohere | 256,000 | $2.50 / $10.00 | passthrough | — | streaming, tools |
| cohere/command-a-plus | cohere | 192,000 | $0.30 / $1.50 | passthrough | — | streaming, tools |
| cohere/command-a-plus-05-2026 | cohere | 128,000 | $0.30 / $1.50 | passthrough | — | streaming, tools, vision |
| cohere/command-a-reasoning-08-2025 | cohere | 256,000 | $2.50 / $10.00 | passthrough | — | streaming, tools |
| cohere/command-a-translate-08-2025 | cohere | 8,192 | $0.50 / $1.50 | passthrough | — | streaming |
| cohere/command-a-vision-07-2025 | cohere | 128,000 | $2.50 / $10.00 | passthrough | — | streaming, tools, vision |
| cohere/command-r-08-2024 | cohere | 128,000 | $0.15 / $0.60 | passthrough | — | streaming, tools |
| cohere/command-r-plus | cohere | 128,000 | $2.50 / $10.00 | passthrough | — | streaming, tools |
| cohere/command-r-plus-08-2024 | cohere | 128,000 | $2.50 / $10.00 | passthrough | — | streaming, tools |
| cohere/command-r7b-12-2024 | cohere | 128,000 | $0.0375 / $0.15 | passthrough | — | streaming, tools |
| cohere/embed-english-light-v3.0 | cohere | 512 | $0.10 / $0.00 | passthrough | — | embedding |
| cohere/embed-english-v3.0 | cohere | 512 | $0.10 / $0.00 | passthrough | — | embedding |
| cohere/embed-multilingual-light-v3.0 | cohere | 512 | $0.10 / $0.00 | passthrough | — | embedding |
| cohere/embed-multilingual-v3.0 | cohere | 512 | $0.10 / $0.00 | passthrough | — | embedding |
| cohere/embed-v4.0 | cohere | 128,000 | $0.12 / $0.00 | passthrough | — | embedding |
| cohere/embed-v5.0-fast | cohere | 128,000 | $0.08 / $0.00 | passthrough | — | embedding |
| cohere/embed-v5.0-pro | cohere | 128,000 | $0.12 / $0.00 | passthrough | — | embedding |
| deepinfra/deepseek-ai/DeepSeek-V4.1-Flash | deepinfra | 1,048,576 | $0.20 / $0.60 | passthrough | — | streaming, tools, vision |
| deepinfra/meta-llama/Llama-3.3-70B-Instruct-Turbo | deepinfra | 131,072 | $0.10 / $0.32 | passthrough | — | streaming, tools |
| deepinfra/openai/gpt-oss-120b | deepinfra | 131,072 | $0.037 / $0.17 | passthrough | — | streaming, tools |
| deepseek/deepseek-chat | deepseek | 128,000 | $0.2574 / $1.0287 | passthrough | — | streaming, tools |
| deepseek/deepseek-chat-v3-0324 | deepseek | 163,840 | $0.29 / $1.140000 | passthrough | — | streaming, tools |
| deepseek/deepseek-chat-v3.1 | deepseek | 163,840 | $0.25 / $0.95 | passthrough | — | streaming, tools |
| deepseek/deepseek-flash | deepseek | 1,048,576 | $0.30 / $1.20 | passthrough | — | streaming, tools, vision |
| deepseek/deepseek-r1 | deepseek | 163,840 | $0.70 / $2.50 | passthrough | — | streaming, tools |
| deepseek/deepseek-r1-0528 | deepseek | 163,840 | $0.50 / $2.150000 | passthrough | — | streaming, tools |
| deepseek/deepseek-r1-distill-llama-70b | deepseek | 128,000 | $0.800000 / $0.800000 | passthrough | — | streaming, tools |
| deepseek/deepseek-reasoner | deepseek | 128,000 | $0.14 / $0.28 | passthrough | — | streaming, tools |
| deepseek/deepseek-v3.1-terminus | deepseek | 163,840 | $0.27 / $1.00 | passthrough | — | streaming, tools |
| deepseek/deepseek-v3.2 | deepseek | 131,072 | $0.28 / $0.42 | passthrough | — | streaming, tools |
| deepseek/deepseek-v3.2-exp | deepseek | 163,840 | $0.27 / $0.41 | passthrough | — | streaming, tools |
| deepseek/deepseek-v4-flash | deepseek | 128,000 | $0.091 / $0.182 | passthrough | — | streaming, tools |
| deepseek/deepseek-v4-flash-0731 | deepseek | 1,048,576 | $0.119 / $0.238 | passthrough | — | streaming, tools |
| deepseek/deepseek-v4-flash-vision-exp | deepseek | 1,048,576 | $0.215600 / $0.6468 | passthrough | — | streaming, tools |
| deepseek/deepseek-v4-pro | deepseek | 1,048,576 | $0.955260 / $1.910520 | passthrough | — | streaming, tools |
| deepseek/deepseek-v4-pro-0813 | deepseek | 1,048,576 | $0.66 / $1.980000 | passthrough | — | streaming, tools |
| deepseek/deepseek-v4.1-flash | deepseek | 1,048,576 | $0.30 / $1.20 | $0.25 / $0.75 | 33 % off | streaming, tools, vision |
| deepseek/deepseek-v4.1-flash-1017 | deepseek | 1,048,576 | $0.15 / $0.60 | passthrough | — | streaming, tools |
| fireworks/llama-v3p3-70b | fireworks | 128,000 | $0.90 / $0.90 | passthrough | — | streaming, tools |
| google/antigravity-preview-latest | google | 1,048,576 | $0.75 / $3.75 | passthrough | — | streaming, tools, vision |
| google/gemini-2.0-flash | google | 1,000,000 | $0.10 / $0.40 | passthrough | — | streaming, tools, vision |
| google/gemini-2.5-flash | google | 1,000,000 | $0.30 / $2.50 | passthrough | — | streaming, tools, vision |
| google/gemini-2.5-flash-image | google | 32,768 | $0.30 / $2.50 | passthrough | — | streaming, tools, vision |
| google/gemini-2.5-flash-lite | google | 1,048,576 | $0.100000 / $0.400000 | passthrough | — | streaming, tools, vision |
| google/gemini-2.5-pro | google | 2,000,000 | $1.25 / $10.00 | passthrough | — | streaming, tools, vision |
| google/gemini-2.5-pro-preview | google | 1,048,576 | $1.25 / $10.00 | passthrough | — | streaming, tools, vision |
| google/gemini-2.5-pro-preview-05-06 | google | 1,048,576 | $1.25 / $10.00 | passthrough | — | streaming, tools, vision |
| google/gemini-3-flash-preview | google | 1,000,000 | $0.50 / $3.00 | passthrough | — | streaming, tools, vision |
| google/gemini-3-pro-image | google | 65,536 | $2.00 / $12.00 | passthrough | — | streaming, tools, vision |
| google/gemini-3-pro-image-preview | google | 65,536 | $2.00 / $12.00 | passthrough | — | streaming, tools, vision |
| google/gemini-3.1-flash-image | google | 131,072 | $0.50 / $3.00 | passthrough | — | streaming, tools, vision |
| google/gemini-3.1-flash-image-preview | google | 131,072 | $0.50 / $3.00 | passthrough | — | streaming, tools, vision |
| google/gemini-3.1-flash-lite | google | 1,000,000 | $0.25 / $1.50 | passthrough | — | streaming, tools, vision |
| google/gemini-3.1-flash-lite-image | google | 65,536 | $0.25 / $1.50 | passthrough | — | streaming, tools, vision |
| google/gemini-3.1-flash-lite-preview | google | 1,048,576 | $0.25 / $1.50 | passthrough | — | streaming, tools, vision |
| google/gemini-3.1-pro | google | 2,000,000 | $2.00 / $12.00 | passthrough | — | streaming, tools, vision |
| google/gemini-3.1-pro-preview | google | 1,048,576 | $2.00 / $12.00 | passthrough | — | streaming, tools, vision |
| google/gemini-3.1-pro-preview-customtools | google | 1,048,756 | $2.00 / $12.00 | passthrough | — | streaming, tools, vision |
| google/gemini-3.5-flash | google | 1,000,000 | $1.50 / $9.00 | passthrough | — | streaming, tools, vision |
| google/gemini-3.5-flash-lite | google | 1,048,576 | $0.30 / $2.50 | passthrough | — | streaming, tools, vision |
| google/gemini-3.6-flash | google | 1,048,576 | $0.75 / $3.75 | passthrough | — | streaming, tools, vision |
| google/gemini-3.7-flash | google | 1,048,576 | $0.75 / $3.75 | passthrough | — | streaming, tools, vision |
| google/gemini-3.8-flash | google | 1,048,576 | $0.825000 / $4.125 | passthrough | — | streaming, tools |
| google/gemini-embedding-001 | google | 2,048 | $0.15 / $0.00 | passthrough | — | embedding |
| google/gemini-embedding-2 | google | 8,192 | $0.20 / $0.00 | passthrough | — | embedding |
| google/gemini-flash-latest | google | 1,048,576 | $0.75 / $3.75 | passthrough | — | streaming, tools, vision |
| google/gemini-flash-lite-latest | google | 1,048,576 | $0.30 / $2.50 | passthrough | — | streaming, tools, vision |
| google/gemini-nano-banana-2.1 | google | 65,536 | $1.50 / $7.50 | passthrough | — | streaming, tools, vision |
| google/gemini-pro-latest | google | 1,048,576 | $2.00 / $12.00 | passthrough | — | streaming, tools, vision |
| google/gemini-robotics-er-2-preview | google | 131,072 | $1.00 / $5.00 | passthrough | — | streaming, tools, vision |
| google/gemma-2-27b-it | google | 8,192 | $0.65 / $0.65 | passthrough | — | streaming, tools |
| google/gemma-3-12b-it | google | 131,072 | $0.050000 / $0.15 | passthrough | — | streaming, tools |
| google/gemma-3-27b-it | google | 131,072 | $0.08 / $0.35 | passthrough | — | streaming, tools |
| google/gemma-3-4b-it | google | 131,072 | $0.050000 / $0.100000 | passthrough | — | streaming, tools |
| google/gemma-3n-e4b-it | google | 32,768 | $0.06 / $0.12 | passthrough | — | streaming, tools |
| google/gemma-4-26b-a4b-it | google | 262,144 | $0.0765 / $0.255 | passthrough | — | streaming, tools |
| google/gemma-4-31b-it | google | 262,144 | $0.200000 / $0.400000 | passthrough | — | streaming, tools |
| groq/openai/gpt-oss-120b | groq | 131,072 | $0.15 / $0.60 | passthrough | — | streaming, tools |
| groq/openai/gpt-oss-20b | groq | 131,072 | $0.075 / $0.30 | passthrough | — | streaming, tools |
| groq/qwen/qwen3.8-27b | groq | 131,072 | $0.80 / $4.00 | passthrough | — | streaming, tools, vision |
| meta-llama/llama-3.3-70b-instruct | meta-llama | 131,072 | $1.04 / $1.04 | $0.125 / $0.40 | 75 % off | streaming, tools |
| mistral/codestral-2508 | mistral | 256,000 | $0.30 / $0.900000 | passthrough | — | streaming, tools |
| mistral/codestral-embed | mistral | 8,192 | $0.15 / $0.00 | passthrough | — | embedding |
| mistral/codestral-embed-2505 | mistral | 8,192 | $0.15 / $0.00 | passthrough | — | embedding |
| mistral/codestral-latest | mistral | 256,000 | $0.30 / $0.90 | passthrough | — | streaming, tools |
| mistral/devstral-2512 | mistral | 262,144 | $0.400000 / $2.00 | passthrough | — | streaming, tools |
| mistral/ministral-14b-2512 | mistral | 262,144 | $0.200000 / $0.200000 | passthrough | — | streaming, tools |
| mistral/ministral-14b-latest | mistral | 262,144 | $0.20 / $0.20 | passthrough | — | streaming, tools, vision |
| mistral/ministral-3b-2512 | mistral | 131,072 | $0.100000 / $0.100000 | passthrough | — | streaming, tools |
| mistral/ministral-3b-latest | mistral | 262,144 | $0.10 / $0.10 | passthrough | — | streaming, tools, vision |
| mistral/ministral-8b | mistral | 128,000 | $0.11 / $0.11 | passthrough | — | streaming, tools |
| mistral/ministral-8b-2512 | mistral | 262,144 | $0.15 / $0.15 | passthrough | — | streaming, tools |
| mistral/ministral-8b-latest | mistral | 262,144 | $0.15 / $0.15 | passthrough | — | streaming, tools, vision |
| mistral/mistral-embed | mistral | 8,192 | $0.10 / $0.00 | passthrough | — | embedding |
| mistral/mistral-embed-2312 | mistral | 8,192 | $0.10 / $0.00 | passthrough | — | embedding |
| mistral/mistral-large | mistral | 128,000 | $2.00 / $6.00 | passthrough | — | streaming, tools |
| mistral/mistral-large-2407 | mistral | 131,072 | $2.00 / $6.00 | passthrough | — | streaming, tools |
| mistral/mistral-large-2512 | mistral | 262,144 | $0.50 / $1.50 | passthrough | — | streaming, tools |
| mistral/mistral-large-4-0 | mistral | 524,288 | $0.680000 / $2.09 | passthrough | — | streaming, tools |
| mistral/mistral-large-latest | mistral | 128,000 | $0.50 / $1.50 | passthrough | — | streaming, tools |
| mistral/mistral-medium-3 | mistral | 131,072 | $0.400000 / $2.00 | passthrough | — | streaming, tools |
| mistral/mistral-medium-3-5 | mistral | 262,144 | $1.50 / $7.50 | passthrough | — | streaming, tools |
| mistral/mistral-medium-3.1 | mistral | 131,072 | $0.400000 / $2.00 | passthrough | — | streaming, tools |
| mistral/mistral-medium-latest | mistral | 262,144 | $1.50 / $7.50 | passthrough | — | streaming, tools, vision |
| mistral/mistral-nemo | mistral | 131,072 | $0.02 / $0.03 | passthrough | — | streaming, tools |
| mistral/mistral-saba | mistral | 32,768 | $0.200000 / $0.60 | passthrough | — | streaming, tools |
| mistral/mistral-small-24b-instruct-2501 | mistral | 32,768 | $0.050000 / $0.08 | passthrough | — | streaming, tools |
| mistral/mistral-small-2603 | mistral | 262,144 | $0.15 / $0.60 | passthrough | — | streaming, tools |
| mistral/mistral-small-3.1-24b-instruct | mistral | 128,000 | $0.351 / $0.555000 | passthrough | — | streaming, tools |
| mistral/mistral-small-3.2-24b-instruct | mistral | 128,000 | $0.075 / $0.200000 | passthrough | — | streaming, tools |
| mistral/mistral-small-latest | mistral | 128,000 | $0.15 / $0.60 | passthrough | — | streaming, tools |
| mistral/mixtral-8x22b-instruct | mistral | 65,536 | $2.00 / $6.00 | passthrough | — | streaming, tools |
| mistral/voxtral-small-24b-2507 | mistral | 32,000 | $0.100000 / $0.30 | passthrough | — | streaming, tools |
| mistral/voxtral-small-2507 | mistral | 32,768 | $0.10 / $0.40 | passthrough | — | streaming, tools |
| mistral/voxtral-small-latest | mistral | 32,768 | $0.10 / $0.40 | passthrough | — | streaming, tools |
| mistral/zai-glm-5-3 | mistral | 1,048,576 | $1.40 / $4.40 | passthrough | — | streaming, tools |
| mistral/zai-glm-latest | mistral | 1,048,576 | $1.40 / $4.40 | passthrough | — | streaming, tools |
| moonshot/kimi-k2 | moonshot | 128,000 | $0.60 / $2.50 | passthrough | — | streaming, tools, vision |
| moonshot/kimi-k2-0905 | moonshot | 262,144 | $0.60 / $2.50 | passthrough | — | streaming, tools, vision |
| moonshot/kimi-k2-thinking | moonshot | 262,144 | $0.60 / $2.50 | passthrough | — | streaming, tools, vision |
| moonshot/kimi-k2.5 | moonshot | 256,000 | $0.60 / $3.00 | passthrough | — | streaming, tools, vision |
| moonshot/kimi-k2.6 | moonshot | 256,000 | $0.95 / $4.00 | passthrough | — | streaming, tools, vision |
| moonshot/kimi-k2.7-code | moonshot | 262,144 | $0.72 / $3.49 | passthrough | — | streaming, tools, vision |
| moonshot/kimi-k2.7-code-highspeed | moonshot | 262,144 | $1.90 / $8.00 | passthrough | — | streaming, tools, vision |
| moonshot/kimi-k3 | moonshot | 1,048,576 | $3.00 / $15.00 | passthrough | — | streaming, tools, vision |
| moonshot/kimi-k3.1-pro | moonshot | 1,048,576 | $2.00 / $10.00 | passthrough | — | streaming, tools |
| openai/gpt-3.5-turbo | openai | 16,385 | $0.50 / $1.50 | passthrough | — | streaming, tools |
| openai/gpt-3.5-turbo-0613 | openai | 4,095 | $1.00 / $2.00 | passthrough | — | streaming, tools |
| openai/gpt-3.5-turbo-16k | openai | 16,385 | $3.00 / $4.00 | passthrough | — | streaming, tools |
| openai/gpt-3.5-turbo-instruct | openai | 4,095 | $1.50 / $2.00 | passthrough | — | streaming, tools |
| openai/gpt-4 | openai | 8,191 | $30.00 / $60.00 | passthrough | — | streaming, tools |
| openai/gpt-4-turbo | openai | 128,000 | $10.00 / $30.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-4-turbo-preview | openai | 128,000 | $10.00 / $30.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-4.1 | openai | 1,000,000 | $2.00 / $8.00 | passthrough | — | streaming, tools |
| openai/gpt-4.1-mini | openai | 1,000,000 | $0.40 / $1.60 | passthrough | — | streaming, tools |
| openai/gpt-4.1-nano | openai | 1,047,576 | $0.100000 / $0.400000 | passthrough | — | streaming, tools |
| openai/gpt-4o | openai | 128,000 | $2.50 / $10.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-4o-2024-05-13 | openai | 128,000 | $5.00 / $15.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-4o-2024-08-06 | openai | 128,000 | $2.50 / $10.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-4o-2024-11-20 | openai | 128,000 | $2.50 / $10.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-4o-mini | openai | 128,000 | $0.15 / $0.60 | passthrough | — | streaming, tools, vision |
| openai/gpt-4o-mini-2024-07-18 | openai | 128,000 | $0.15 / $0.60 | passthrough | — | streaming, tools, vision |
| openai/gpt-4o-mini-search-preview | openai | 128,000 | $0.15 / $0.60 | passthrough | — | streaming, tools, vision |
| openai/gpt-4o-search-preview | openai | 128,000 | $2.50 / $10.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5 | openai | 400,000 | $1.25 / $10.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5-chat | openai | 128,000 | $1.25 / $10.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5-image | openai | 400,000 | $10.00 / $10.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5-image-mini | openai | 400,000 | $2.50 / $2.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5-mini | openai | 400,000 | $0.25 / $2.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5-nano | openai | 400,000 | $0.050000 / $0.400000 | passthrough | — | streaming, tools, vision |
| openai/gpt-5-pro | openai | 400,000 | $15.00 / $120.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5-search-api | openai | 400,000 | $1.25 / $10.00 | passthrough | — | streaming, tools |
| openai/gpt-5.1 | openai | 400,000 | $1.25 / $10.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.1-chat | openai | 128,000 | $1.25 / $10.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.2 | openai | 400,000 | $1.75 / $14.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.2-chat | openai | 128,000 | $1.75 / $14.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.2-pro | openai | 400,000 | $21.00 / $168.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.3-chat | openai | 128,000 | $1.75 / $14.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.3-codex | openai | 400,000 | $1.75 / $14.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.4 | openai | 400,000 | $2.50 / $15.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.4-image-2 | openai | 272,000 | $8.00 / $15.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.4-mini | openai | 400,000 | $0.75 / $4.50 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.4-nano | openai | 400,000 | $0.200000 / $1.25 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.4-pro | openai | 1,050,000 | $30.00 / $180.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.5 | openai | 400,000 | $5.00 / $30.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.5-pro | openai | 1,050,000 | $30.00 / $180.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.6-luna | openai | 1,000,000 | $0.200000 / $1.20 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.6-luna-pro | openai | 1,050,000 | $0.200000 / $1.20 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.6-sol | openai | 1,000,000 | $4.00 / $20.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.6-sol-pro | openai | 1,050,000 | $4.00 / $20.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.6-terra | openai | 1,000,000 | $2.00 / $12.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-5.6-terra-pro | openai | 1,050,000 | $2.00 / $12.00 | passthrough | — | streaming, tools, vision |
| openai/gpt-6-astra | openai | 1,050,000 | $10.00 / $50.00 | passthrough | — | streaming, tools |
| openai/gpt-6-astra-pro | openai | 1,050,000 | $10.00 / $50.00 | passthrough | — | streaming, tools |
| openai/gpt-6-luna | openai | 1,050,000 | $0.100000 / $0.50 | passthrough | — | streaming, tools |
| openai/gpt-6-luna-pro | openai | 1,050,000 | $0.100000 / $0.50 | passthrough | — | streaming, tools |
| openai/gpt-6-sol | openai | 1,050,000 | $2.00 / $10.00 | passthrough | — | streaming, tools |
| openai/gpt-6-sol-pro | openai | 1,050,000 | $2.00 / $10.00 | passthrough | — | streaming, tools |
| openai/gpt-6.1-sol | openai | 1,050,000 | $2.00 / $10.00 | passthrough | — | streaming, tools |
| openai/gpt-6.1-sol-pro | openai | 1,050,000 | $2.00 / $10.00 | passthrough | — | streaming, tools |
| openai/gpt-6.1-terra | openai | 1,050,000 | $1.00 / $5.00 | passthrough | — | streaming, tools |
| openai/gpt-6.1-terra-pro | openai | 1,050,000 | $1.00 / $5.00 | passthrough | — | streaming, tools |
| openai/gpt-audio | openai | 128,000 | $2.50 / $10.00 | passthrough | — | streaming, tools |
| openai/gpt-audio-mini | openai | 128,000 | $0.60 / $2.40 | passthrough | — | streaming, tools |
| openai/gpt-chat-latest | openai | 400,000 | $5.00 / $30.00 | passthrough | — | streaming, tools |
| openai/gpt-oss-120b | openai | 131,072 | $0.15 / $0.60 | $0.0463 / $0.2125 | 65 % off | streaming, tools |
| openai/o1 | openai | 200,000 | $15.00 / $60.00 | passthrough | — | streaming, tools |
| openai/o1-mini | openai | 128,000 | $1.10 / $4.40 | passthrough | — | streaming |
| openai/o1-pro | openai | 200,000 | $150.00 / $600.00 | passthrough | — | streaming, tools |
| openai/o3 | openai | 200,000 | $2.00 / $8.00 | passthrough | — | streaming, tools |
| openai/o3-mini | openai | 200,000 | $1.10 / $4.40 | passthrough | — | streaming, tools |
| openai/o3-mini-high | openai | 200,000 | $1.10 / $4.40 | passthrough | — | streaming, tools |
| openai/o3-pro | openai | 200,000 | $20.00 / $80.00 | passthrough | — | streaming, tools |
| openai/o4-mini | openai | 200,000 | $1.10 / $4.40 | passthrough | — | streaming, tools |
| openai/o4-mini-high | openai | 200,000 | $1.10 / $4.40 | passthrough | — | streaming, tools |
| openai/text-embedding-3-large | openai | 8,191 | $0.13 / $0.00 | passthrough | — | embedding |
| openai/text-embedding-3-small | openai | 8,191 | $0.02 / $0.00 | passthrough | — | embedding |
| openrouter/ai21/jamba-large-1.7 | openrouter | 256,000 | $2.11 / $8.44 | passthrough | — | streaming, tools |
| openrouter/aion-labs/aion-2.0 | openrouter | 131,072 | $0.844000 / $1.688000 | passthrough | — | streaming, tools |
| openrouter/aion-labs/aion-rp-llama-3.1-8b | openrouter | 32,768 | $0.844000 / $1.688000 | passthrough | — | streaming, tools |
| openrouter/allenai/olmo-3-32b-think | openrouter | 65,536 | $0.158250 / $0.5275 | passthrough | — | streaming, tools |
| openrouter/amazon/nova-2-lite-v1 | openrouter | 1,000,000 | $0.316500 / $2.637500 | passthrough | — | streaming, tools |
| openrouter/amazon/nova-lite-v1 | openrouter | 300,000 | $0.0633 / $0.2532 | passthrough | — | streaming, tools |
| openrouter/amazon/nova-micro-v1 | openrouter | 128,000 | $0.036925 / $0.1477 | passthrough | — | streaming, tools |
| openrouter/amazon/nova-premier-v1 | openrouter | 1,000,000 | $2.637500 / $13.1875 | passthrough | — | streaming, tools |
| openrouter/amazon/nova-pro-v1 | openrouter | 300,000 | $0.844000 / $3.376000 | passthrough | — | streaming, tools |
| openrouter/anthracite-org/magnum-v4-72b | openrouter | 32,768 | $3.165 / $5.275000 | passthrough | — | streaming, tools |
| openrouter/anthropic/claude-3-haiku | openrouter | 200,000 | $0.26375 / $1.318750 | passthrough | — | streaming, tools |
| openrouter/anthropic/claude-haiku-4.5 | openrouter | 200,000 | $1.055 / $5.275000 | passthrough | — | streaming, tools |
| openrouter/anthropic/claude-opus-4 | openrouter | 200,000 | $15.825 / $79.125 | passthrough | — | streaming, tools |
| openrouter/anthropic/claude-opus-4.1 | openrouter | 200,000 | $15.825 / $79.125 | passthrough | — | streaming, tools |
| openrouter/anthropic/claude-opus-4.5 | openrouter | 200,000 | $5.275000 / $26.375 | passthrough | — | streaming, tools |
| openrouter/anthropic/claude-opus-4.6 | openrouter | 1,000,000 | $5.275000 / $26.375 | passthrough | — | streaming, tools |
| openrouter/anthropic/claude-opus-4.7 | openrouter | 1,000,000 | $5.275000 / $26.375 | passthrough | — | streaming, tools |
| openrouter/anthropic/claude-opus-4.7-fast | openrouter | 1,000,000 | $31.65 / $158.25 | passthrough | — | streaming, tools |
| openrouter/anthropic/claude-sonnet-4 | openrouter | 1,000,000 | $3.165 / $15.825 | passthrough | — | streaming, tools |
| openrouter/anthropic/claude-sonnet-4.5 | openrouter | 1,000,000 | $3.165 / $15.825 | passthrough | — | streaming, tools |
| openrouter/anthropic/claude-sonnet-4.6 | openrouter | 1,000,000 | $3.165 / $15.825 | passthrough | — | streaming, tools |
| openrouter/arcee-ai/coder-large | openrouter | 32,768 | $0.5275 / $0.844000 | passthrough | — | streaming, tools |
| openrouter/arcee-ai/trinity-large-thinking | openrouter | 262,144 | $0.26375 / $0.844000 | passthrough | — | streaming, tools |
| openrouter/arcee-ai/virtuoso-large | openrouter | 131,072 | $0.79125 / $1.266000 | passthrough | — | streaming, tools |
| openrouter/baidu/ernie-4.5-vl-424b-a47b | openrouter | 131,072 | $0.443100 / $1.318750 | passthrough | — | streaming, tools |
| openrouter/bytedance-seed/seed-1.6 | openrouter | 262,144 | $0.26375 / $2.11 | passthrough | — | streaming, tools |
| openrouter/bytedance-seed/seed-1.6-flash | openrouter | 262,144 | $0.079125 / $0.316500 | passthrough | — | streaming, tools |
| openrouter/bytedance-seed/seed-2.0-lite | openrouter | 262,144 | $0.26375 / $2.11 | passthrough | — | streaming, tools |
| openrouter/bytedance-seed/seed-2.0-mini | openrouter | 262,144 | $0.105500 / $0.422000 | passthrough | — | streaming, tools |
| openrouter/bytedance/ui-tars-1.5-7b | openrouter | 128,000 | $0.105500 / $0.211000 | passthrough | — | streaming, tools |
| openrouter/cohere/command-a | openrouter | 256,000 | $2.637500 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/cohere/command-r-08-2024 | openrouter | 128,000 | $0.158250 / $0.633000 | passthrough | — | streaming, tools |
| openrouter/cohere/command-r-plus-08-2024 | openrouter | 128,000 | $2.637500 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/cohere/command-r7b-12-2024 | openrouter | 128,000 | $0.039562 / $0.158250 | passthrough | — | streaming, tools |
| openrouter/deepcogito/cogito-v2.1-671b | openrouter | 128,000 | $1.318750 / $1.318750 | passthrough | — | streaming, tools |
| openrouter/deepseek/deepseek-chat | openrouter | 131,072 | $0.211211 / $0.844105 | passthrough | — | streaming, tools |
| openrouter/deepseek/deepseek-chat-v3-0324 | openrouter | 163,840 | $0.2532 / $0.949500 | passthrough | — | streaming, tools |
| openrouter/deepseek/deepseek-chat-v3.1 | openrouter | 163,840 | $0.221550 / $0.833450 | passthrough | — | streaming, tools |
| openrouter/deepseek/deepseek-r1 | openrouter | 163,840 | $0.738500 / $2.637500 | passthrough | — | streaming, tools |
| openrouter/deepseek/deepseek-r1-0528 | openrouter | 163,840 | $0.5275 / $2.26825 | passthrough | — | streaming, tools |
| openrouter/deepseek/deepseek-r1-distill-llama-70b | openrouter | 128,000 | $0.844000 / $0.844000 | passthrough | — | streaming, tools |
| openrouter/deepseek/deepseek-v3.1-terminus | openrouter | 163,840 | $0.28485 / $1.002250 | passthrough | — | streaming, tools |
| openrouter/deepseek/deepseek-v3.2 | openrouter | 131,072 | $0.226297 / $0.339446 | passthrough | — | streaming, tools |
| openrouter/deepseek/deepseek-v3.2-exp | openrouter | 163,840 | $0.28485 / $0.432550 | passthrough | — | streaming, tools |
| openrouter/deepseek/deepseek-v4-flash | openrouter | 1,048,576 | $0.088620 / $0.177240 | passthrough | — | streaming, tools |
| openrouter/deepseek/deepseek-v4-pro | openrouter | 1,048,576 | $0.458925 / $0.917850 | passthrough | — | streaming, tools |
| openrouter/google/gemini-2.5-flash | openrouter | 1,048,576 | $0.316500 / $2.637500 | passthrough | — | streaming, tools |
| openrouter/google/gemini-2.5-flash-image | openrouter | 32,768 | $0.316500 / $2.637500 | passthrough | — | streaming, tools |
| openrouter/google/gemini-2.5-flash-lite | openrouter | 1,048,576 | $0.105500 / $0.422000 | passthrough | — | streaming, tools |
| openrouter/google/gemini-2.5-pro | openrouter | 1,048,576 | $1.318750 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/google/gemini-2.5-pro-preview | openrouter | 1,048,576 | $1.318750 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/google/gemini-2.5-pro-preview-05-06 | openrouter | 1,048,576 | $1.318750 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/google/gemini-3-flash-preview | openrouter | 1,048,576 | $0.5275 / $3.165 | passthrough | — | streaming, tools |
| openrouter/google/gemini-3-pro-image-preview | openrouter | 65,536 | $2.11 / $12.66 | passthrough | — | streaming, tools |
| openrouter/google/gemini-3.1-flash-image-preview | openrouter | 131,072 | $0.5275 / $3.165 | passthrough | — | streaming, tools |
| openrouter/google/gemini-3.1-flash-lite | openrouter | 1,048,576 | $0.26375 / $1.5825 | passthrough | — | streaming, tools |
| openrouter/google/gemini-3.1-flash-lite-preview | openrouter | 1,048,576 | $0.26375 / $1.5825 | passthrough | — | streaming, tools |
| openrouter/google/gemini-3.1-pro-preview | openrouter | 1,048,576 | $2.11 / $12.66 | passthrough | — | streaming, tools |
| openrouter/google/gemini-3.1-pro-preview-customtools | openrouter | 1,048,756 | $2.11 / $12.66 | passthrough | — | streaming, tools |
| openrouter/google/gemini-3.5-flash | openrouter | 1,048,576 | $1.5825 / $9.495 | passthrough | — | streaming, tools |
| openrouter/google/gemma-2-27b-it | openrouter | 8,192 | $0.68575 / $0.68575 | passthrough | — | streaming, tools |
| openrouter/google/gemma-3-12b-it | openrouter | 131,072 | $0.052750 / $0.158250 | passthrough | — | streaming, tools |
| openrouter/google/gemma-3-27b-it | openrouter | 131,072 | $0.0844 / $0.1688 | passthrough | — | streaming, tools |
| openrouter/google/gemma-3-4b-it | openrouter | 131,072 | $0.052750 / $0.105500 | passthrough | — | streaming, tools |
| openrouter/google/gemma-3n-e4b-it | openrouter | 32,768 | $0.0633 / $0.1266 | passthrough | — | streaming, tools |
| openrouter/google/gemma-4-26b-a4b-it | openrouter | 262,144 | $0.0633 / $0.34815 | passthrough | — | streaming, tools |
| openrouter/google/gemma-4-31b-it | openrouter | 262,144 | $0.1266 / $0.369250 | passthrough | — | streaming, tools |
| openrouter/gryphe/mythomax-l2-13b | openrouter | 4,096 | $0.0633 / $0.0633 | passthrough | — | streaming, tools |
| openrouter/ibm-granite/granite-4.0-h-micro | openrouter | 131,000 | $0.017935 / $0.11816 | passthrough | — | streaming, tools |
| openrouter/ibm-granite/granite-4.1-8b | openrouter | 131,072 | $0.052750 / $0.105500 | passthrough | — | streaming, tools |
| openrouter/inception/mercury-2 | openrouter | 128,000 | $0.26375 / $0.79125 | passthrough | — | streaming, tools |
| openrouter/inclusionai/ling-2.6-1t | openrouter | 262,144 | $0.079125 / $0.659375 | passthrough | — | streaming, tools |
| openrouter/inclusionai/ling-2.6-flash | openrouter | 262,144 | $0.01055 / $0.03165 | passthrough | — | streaming, tools |
| openrouter/inclusionai/ring-2.6-1t | openrouter | 262,144 | $0.079125 / $0.659375 | passthrough | — | streaming, tools |
| openrouter/inflection/inflection-3-pi | openrouter | 8,000 | $2.637500 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/inflection/inflection-3-productivity | openrouter | 8,000 | $2.637500 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/kwaipilot/kat-coder-pro-v2 | openrouter | 256,000 | $0.316500 / $1.266000 | passthrough | — | streaming, tools |
| openrouter/mancer/weaver | openrouter | 8,000 | $0.79125 / $1.055 | passthrough | — | streaming, tools |
| openrouter/meta-llama/llama-3-8b-instruct | openrouter | 8,192 | $0.1477 / $0.1477 | passthrough | — | streaming, tools |
| openrouter/meta-llama/llama-3.1-70b-instruct | openrouter | 131,072 | $0.422000 / $0.422000 | passthrough | — | streaming, tools |
| openrouter/meta-llama/llama-3.1-8b-instruct | openrouter | 131,072 | $0.0211 / $0.03165 | passthrough | — | streaming, tools |
| openrouter/meta-llama/llama-3.2-11b-vision-instruct | openrouter | 131,072 | $0.363975 / $0.363975 | passthrough | — | streaming, tools |
| openrouter/meta-llama/llama-3.2-1b-instruct | openrouter | 131,072 | $0.028485 / $0.212055 | passthrough | — | streaming, tools |
| openrouter/meta-llama/llama-3.2-3b-instruct | openrouter | 131,072 | $0.052750 / $0.34815 | passthrough | — | streaming, tools |
| openrouter/meta-llama/llama-3.3-70b-instruct | openrouter | 131,072 | $0.105500 / $0.3376 | passthrough | — | streaming, tools |
| openrouter/meta-llama/llama-4-maverick | openrouter | 1,048,576 | $0.158250 / $0.633000 | passthrough | — | streaming, tools |
| openrouter/meta-llama/llama-4-scout | openrouter | 10,000,000 | $0.105500 / $0.316500 | passthrough | — | streaming, tools |
| openrouter/meta-llama/llama-guard-4-12b | openrouter | 163,840 | $0.189900 / $0.189900 | passthrough | — | streaming, tools |
| openrouter/microsoft/phi-4 | openrouter | 16,384 | $0.07385 / $0.1477 | passthrough | — | streaming, tools |
| openrouter/microsoft/wizardlm-2-8x22b | openrouter | 65,536 | $0.654100 / $0.654100 | passthrough | — | streaming, tools |
| openrouter/minimax/minimax-01 | openrouter | 1,000,192 | $0.211000 / $1.1605 | passthrough | — | streaming, tools |
| openrouter/minimax/minimax-m1 | openrouter | 1,000,000 | $0.422000 / $2.321 | passthrough | — | streaming, tools |
| openrouter/minimax/minimax-m2 | openrouter | 204,800 | $0.269025 / $1.0761 | passthrough | — | streaming, tools |
| openrouter/minimax/minimax-m2-her | openrouter | 65,536 | $0.316500 / $1.266000 | passthrough | — | streaming, tools |
| openrouter/minimax/minimax-m2.1 | openrouter | 204,800 | $0.316500 / $1.266000 | passthrough | — | streaming, tools |
| openrouter/minimax/minimax-m2.5 | openrouter | 204,800 | $0.158250 / $0.949500 | passthrough | — | streaming, tools |
| openrouter/minimax/minimax-m2.7 | openrouter | 204,800 | $0.2532 / $1.0128 | passthrough | — | streaming, tools |
| openrouter/mistralai/codestral-2508 | openrouter | 256,000 | $0.316500 / $0.949500 | passthrough | — | streaming, tools |
| openrouter/mistralai/devstral-2512 | openrouter | 262,144 | $0.422000 / $2.11 | passthrough | — | streaming, tools |
| openrouter/mistralai/ministral-14b-2512 | openrouter | 262,144 | $0.211000 / $0.211000 | passthrough | — | streaming, tools |
| openrouter/mistralai/ministral-3b-2512 | openrouter | 131,072 | $0.105500 / $0.105500 | passthrough | — | streaming, tools |
| openrouter/mistralai/ministral-8b-2512 | openrouter | 262,144 | $0.158250 / $0.158250 | passthrough | — | streaming, tools |
| openrouter/mistralai/mistral-large | openrouter | 128,000 | $2.11 / $6.33 | passthrough | — | streaming, tools |
| openrouter/mistralai/mistral-large-2407 | openrouter | 131,072 | $2.11 / $6.33 | passthrough | — | streaming, tools |
| openrouter/mistralai/mistral-large-2512 | openrouter | 262,144 | $0.5275 / $1.5825 | passthrough | — | streaming, tools |
| openrouter/mistralai/mistral-medium-3 | openrouter | 131,072 | $0.422000 / $2.11 | passthrough | — | streaming, tools |
| openrouter/mistralai/mistral-medium-3-5 | openrouter | 262,144 | $1.5825 / $7.9125 | passthrough | — | streaming, tools |
| openrouter/mistralai/mistral-medium-3.1 | openrouter | 131,072 | $0.422000 / $2.11 | passthrough | — | streaming, tools |
| openrouter/mistralai/mistral-nemo | openrouter | 131,072 | $0.0211 / $0.03165 | passthrough | — | streaming, tools |
| openrouter/mistralai/mistral-saba | openrouter | 32,768 | $0.211000 / $0.633000 | passthrough | — | streaming, tools |
| openrouter/mistralai/mistral-small-24b-instruct-2501 | openrouter | 32,768 | $0.052750 / $0.0844 | passthrough | — | streaming, tools |
| openrouter/mistralai/mistral-small-2603 | openrouter | 262,144 | $0.158250 / $0.633000 | passthrough | — | streaming, tools |
| openrouter/mistralai/mistral-small-3.1-24b-instruct | openrouter | 128,000 | $0.370305 / $0.585525 | passthrough | — | streaming, tools |
| openrouter/mistralai/mistral-small-3.2-24b-instruct | openrouter | 128,000 | $0.079125 / $0.211000 | passthrough | — | streaming, tools |
| openrouter/mistralai/mixtral-8x22b-instruct | openrouter | 65,536 | $2.11 / $6.33 | passthrough | — | streaming, tools |
| openrouter/mistralai/voxtral-small-24b-2507 | openrouter | 32,000 | $0.105500 / $0.316500 | passthrough | — | streaming, tools |
| openrouter/moonshotai/kimi-k2 | openrouter | 131,072 | $0.60135 / $2.4265 | passthrough | — | streaming, tools |
| openrouter/moonshotai/kimi-k2-0905 | openrouter | 262,144 | $0.633000 / $2.637500 | passthrough | — | streaming, tools |
| openrouter/moonshotai/kimi-k2-thinking | openrouter | 262,144 | $0.633000 / $2.637500 | passthrough | — | streaming, tools |
| openrouter/moonshotai/kimi-k2.5 | openrouter | 262,144 | $0.395625 / $2.136375 | passthrough | — | streaming, tools |
| openrouter/moonshotai/kimi-k2.6 | openrouter | 262,144 | $0.6963 / $3.59755 | passthrough | — | streaming, tools |
| openrouter/morph/morph-v3-fast | openrouter | 81,920 | $0.844000 / $1.266000 | passthrough | — | streaming, tools |
| openrouter/morph/morph-v3-large | openrouter | 262,144 | $0.949500 / $2.004500 | passthrough | — | streaming, tools |
| openrouter/nousresearch/hermes-3-llama-3.1-405b | openrouter | 131,072 | $1.055 / $1.055 | passthrough | — | streaming, tools |
| openrouter/nousresearch/hermes-3-llama-3.1-70b | openrouter | 131,072 | $0.738500 / $0.738500 | passthrough | — | streaming, tools |
| openrouter/nousresearch/hermes-4-405b | openrouter | 131,072 | $1.055 / $3.165 | passthrough | — | streaming, tools |
| openrouter/nousresearch/hermes-4-70b | openrouter | 131,072 | $0.13715 / $0.422000 | passthrough | — | streaming, tools |
| openrouter/nvidia/llama-3.3-nemotron-super-49b-v1.5 | openrouter | 131,072 | $0.422000 / $0.422000 | passthrough | — | streaming, tools |
| openrouter/nvidia/nemotron-3-nano-30b-a3b | openrouter | 262,144 | $0.052750 / $0.211000 | passthrough | — | streaming, tools |
| openrouter/nvidia/nemotron-3-super-120b-a12b | openrouter | 1,000,000 | $0.0844 / $0.474750 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-3.5-turbo | openrouter | 16,385 | $0.5275 / $1.5825 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-3.5-turbo-0613 | openrouter | 4,095 | $1.055 / $2.11 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-3.5-turbo-16k | openrouter | 16,385 | $3.165 / $4.22 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-3.5-turbo-instruct | openrouter | 4,095 | $1.5825 / $2.11 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-4 | openrouter | 8,191 | $31.65 / $63.30 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-4-turbo | openrouter | 128,000 | $10.550000 / $31.65 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-4-turbo-preview | openrouter | 128,000 | $10.550000 / $31.65 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-4.1 | openrouter | 1,047,576 | $2.11 / $8.44 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-4.1-mini | openrouter | 1,047,576 | $0.422000 / $1.688000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-4.1-nano | openrouter | 1,047,576 | $0.105500 / $0.422000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-4o | openrouter | 128,000 | $2.637500 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-4o-2024-05-13 | openrouter | 128,000 | $5.275000 / $15.825 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-4o-2024-08-06 | openrouter | 128,000 | $2.637500 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-4o-2024-11-20 | openrouter | 128,000 | $2.637500 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-4o-mini | openrouter | 128,000 | $0.158250 / $0.633000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-4o-mini-2024-07-18 | openrouter | 128,000 | $0.158250 / $0.633000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-4o-mini-search-preview | openrouter | 128,000 | $0.158250 / $0.633000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-4o-search-preview | openrouter | 128,000 | $2.637500 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5 | openrouter | 400,000 | $1.318750 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5-chat | openrouter | 128,000 | $1.318750 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5-codex | openrouter | 400,000 | $1.318750 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5-image | openrouter | 400,000 | $10.550000 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5-image-mini | openrouter | 400,000 | $2.637500 / $2.11 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5-mini | openrouter | 400,000 | $0.26375 / $2.11 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5-nano | openrouter | 400,000 | $0.052750 / $0.422000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5-pro | openrouter | 400,000 | $15.825 / $126.60 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.1 | openrouter | 400,000 | $1.318750 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.1-chat | openrouter | 128,000 | $1.318750 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.1-codex | openrouter | 400,000 | $1.318750 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.1-codex-max | openrouter | 400,000 | $1.318750 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.1-codex-mini | openrouter | 400,000 | $0.26375 / $2.11 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.2 | openrouter | 400,000 | $1.84625 / $14.77 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.2-chat | openrouter | 128,000 | $1.84625 / $14.77 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.2-codex | openrouter | 400,000 | $1.84625 / $14.77 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.2-pro | openrouter | 400,000 | $22.155000 / $177.240000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.3-chat | openrouter | 128,000 | $1.84625 / $14.77 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.3-codex | openrouter | 400,000 | $1.84625 / $14.77 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.4 | openrouter | 1,050,000 | $2.637500 / $15.825 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.4-image-2 | openrouter | 272,000 | $8.44 / $15.825 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.4-mini | openrouter | 400,000 | $0.79125 / $4.7475 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.4-nano | openrouter | 400,000 | $0.211000 / $1.318750 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.4-pro | openrouter | 1,050,000 | $31.65 / $189.900000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.5 | openrouter | 1,050,000 | $5.275000 / $31.65 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-5.5-pro | openrouter | 1,050,000 | $31.65 / $189.900000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-audio | openrouter | 128,000 | $2.637500 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-audio-mini | openrouter | 128,000 | $0.633000 / $2.532000 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-chat-latest | openrouter | 400,000 | $5.275000 / $31.65 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-oss-120b | openrouter | 131,072 | $0.037980 / $0.189900 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-oss-20b | openrouter | 131,072 | $0.030595 / $0.1477 | passthrough | — | streaming, tools |
| openrouter/openai/gpt-oss-safeguard-20b | openrouter | 131,072 | $0.079125 / $0.316500 | passthrough | — | streaming, tools |
| openrouter/openai/o1 | openrouter | 200,000 | $15.825 / $63.30 | passthrough | — | streaming, tools |
| openrouter/openai/o1-pro | openrouter | 200,000 | $158.25 / $633.00 | passthrough | — | streaming, tools |
| openrouter/openai/o3 | openrouter | 200,000 | $2.11 / $8.44 | passthrough | — | streaming, tools |
| openrouter/openai/o3-deep-research | openrouter | 200,000 | $10.550000 / $42.200000 | passthrough | — | streaming, tools |
| openrouter/openai/o3-mini | openrouter | 200,000 | $1.1605 / $4.642 | passthrough | — | streaming, tools |
| openrouter/openai/o3-mini-high | openrouter | 200,000 | $1.1605 / $4.642 | passthrough | — | streaming, tools |
| openrouter/openai/o3-pro | openrouter | 200,000 | $21.100000 / $84.400000 | passthrough | — | streaming, tools |
| openrouter/openai/o4-mini | openrouter | 200,000 | $1.1605 / $4.642 | passthrough | — | streaming, tools |
| openrouter/openai/o4-mini-deep-research | openrouter | 200,000 | $2.11 / $8.44 | passthrough | — | streaming, tools |
| openrouter/openai/o4-mini-high | openrouter | 200,000 | $1.1605 / $4.642 | passthrough | — | streaming, tools |
| openrouter/perceptron/perceptron-mk1 | openrouter | 32,768 | $0.158250 / $1.5825 | passthrough | — | streaming, tools |
| openrouter/perplexity/sonar | openrouter | 127,072 | $1.055 / $1.055 | passthrough | — | streaming, tools |
| openrouter/perplexity/sonar-deep-research | openrouter | 128,000 | $2.11 / $8.44 | passthrough | — | streaming, tools |
| openrouter/perplexity/sonar-pro | openrouter | 200,000 | $3.165 / $15.825 | passthrough | — | streaming, tools |
| openrouter/perplexity/sonar-pro-search | openrouter | 200,000 | $3.165 / $15.825 | passthrough | — | streaming, tools |
| openrouter/perplexity/sonar-reasoning-pro | openrouter | 128,000 | $2.11 / $8.44 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen-2.5-72b-instruct | openrouter | 131,072 | $0.379800 / $0.422000 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen-2.5-7b-instruct | openrouter | 131,072 | $0.0422 / $0.105500 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen-2.5-coder-32b-instruct | openrouter | 128,000 | $0.6963 / $1.055 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen-plus | openrouter | 1,000,000 | $0.2743 / $0.8229 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen-plus-2025-07-28 | openrouter | 1,000,000 | $0.2743 / $0.8229 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen2.5-vl-72b-instruct | openrouter | 131,072 | $0.844000 / $1.055 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-14b | openrouter | 131,702 | $0.105500 / $0.2532 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-235b-a22b | openrouter | 131,072 | $0.480025 / $1.920100 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-235b-a22b-2507 | openrouter | 262,144 | $0.094950 / $0.105500 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-235b-a22b-thinking-2507 | openrouter | 262,144 | $0.157723 / $1.577225 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-30b-a3b | openrouter | 131,072 | $0.1266 / $0.5275 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-30b-a3b-instruct-2507 | openrouter | 131,072 | $0.050798 / $0.203668 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-30b-a3b-thinking-2507 | openrouter | 131,072 | $0.13715 / $1.6458 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-32b | openrouter | 131,072 | $0.0844 / $0.2954 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-8b | openrouter | 131,072 | $0.123435 / $0.480025 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-coder | openrouter | 1,048,576 | $0.2321 / $1.899000 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-coder-30b-a3b-instruct | openrouter | 160,000 | $0.07385 / $0.28485 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-coder-flash | openrouter | 1,000,000 | $0.205725 / $1.028625 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-coder-next | openrouter | 262,144 | $0.11605 / $0.844000 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-coder-plus | openrouter | 1,000,000 | $0.68575 / $3.42875 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-max | openrouter | 262,144 | $0.8229 / $4.1145 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-max-thinking | openrouter | 262,144 | $0.8229 / $4.1145 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-next-80b-a3b-instruct | openrouter | 262,144 | $0.094950 / $1.1605 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-next-80b-a3b-thinking | openrouter | 262,144 | $0.102862 / $0.8229 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-vl-235b-a22b-instruct | openrouter | 262,144 | $0.211000 / $0.9284 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-vl-235b-a22b-thinking | openrouter | 131,072 | $0.2743 / $2.743 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-vl-30b-a3b-instruct | openrouter | 262,144 | $0.13715 / $0.5486 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-vl-30b-a3b-thinking | openrouter | 131,072 | $0.13715 / $1.6458 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-vl-32b-instruct | openrouter | 262,144 | $0.10972 / $0.43888 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-vl-8b-instruct | openrouter | 256,000 | $0.123435 / $0.480025 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3-vl-8b-thinking | openrouter | 256,000 | $0.123435 / $1.440075 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3.5-122b-a10b | openrouter | 262,144 | $0.2743 / $2.1944 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3.5-27b | openrouter | 262,144 | $0.205725 / $1.6458 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3.5-35b-a3b | openrouter | 262,144 | $0.1477 / $1.055 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3.5-397b-a17b | openrouter | 256,000 | $0.406175 / $2.584750 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3.5-9b | openrouter | 262,144 | $0.105500 / $0.158250 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3.5-flash-02-23 | openrouter | 1,000,000 | $0.068575 / $0.2743 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3.5-plus-02-15 | openrouter | 1,000,000 | $0.2743 / $1.6458 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3.5-plus-20260420 | openrouter | 1,000,000 | $0.316500 / $1.899000 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3.6-27b | openrouter | 262,144 | $0.300675 / $2.532000 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3.6-35b-a3b | openrouter | 262,144 | $0.1477 / $1.055 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3.6-flash | openrouter | 1,000,000 | $0.197813 / $1.186875 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3.6-max-preview | openrouter | 262,144 | $1.0972 / $6.5832 | passthrough | — | streaming, tools |
| openrouter/qwen/qwen3.6-plus | openrouter | 1,000,000 | $0.342875 / $2.05725 | passthrough | — | streaming, tools |
| openrouter/rekaai/reka-edge | openrouter | 16,384 | $0.105500 / $0.105500 | passthrough | — | streaming, tools |
| openrouter/rekaai/reka-flash-3 | openrouter | 65,536 | $0.105500 / $0.211000 | passthrough | — | streaming, tools |
| openrouter/relace/relace-apply-3 | openrouter | 256,000 | $0.896750 / $1.318750 | passthrough | — | streaming, tools |
| openrouter/relace/relace-search | openrouter | 256,000 | $1.055 / $3.165 | passthrough | — | streaming, tools |
| openrouter/sao10k/l3-lunaris-8b | openrouter | 8,192 | $0.0422 / $0.052750 | passthrough | — | streaming, tools |
| openrouter/sao10k/l3.1-70b-hanami-x1 | openrouter | 16,000 | $3.165 / $3.165 | passthrough | — | streaming, tools |
| openrouter/sao10k/l3.1-euryale-70b | openrouter | 131,072 | $0.896750 / $0.896750 | passthrough | — | streaming, tools |
| openrouter/sao10k/l3.3-euryale-70b | openrouter | 131,072 | $0.68575 / $0.79125 | passthrough | — | streaming, tools |
| openrouter/stepfun/step-3.5-flash | openrouter | 262,144 | $0.105500 / $0.316500 | passthrough | — | streaming, tools |
| openrouter/tencent/hunyuan-a13b-instruct | openrouter | 131,072 | $0.1477 / $0.60135 | passthrough | — | streaming, tools |
| openrouter/tencent/hy3-preview | openrouter | 262,144 | $0.066465 / $0.221550 | passthrough | — | streaming, tools |
| openrouter/thedrummer/cydonia-24b-v4.1 | openrouter | 131,072 | $0.316500 / $0.5275 | passthrough | — | streaming, tools |
| openrouter/thedrummer/rocinante-12b | openrouter | 65,536 | $0.26375 / $0.5275 | passthrough | — | streaming, tools |
| openrouter/thedrummer/skyfall-36b-v2 | openrouter | 32,768 | $0.58025 / $0.844000 | passthrough | — | streaming, tools |
| openrouter/thedrummer/unslopnemo-12b | openrouter | 32,768 | $0.422000 / $0.422000 | passthrough | — | streaming, tools |
| openrouter/undi95/remm-slerp-l2-13b | openrouter | 6,144 | $0.474750 / $0.68575 | passthrough | — | streaming, tools |
| openrouter/upstage/solar-pro-3 | openrouter | 128,000 | $0.158250 / $0.633000 | passthrough | — | streaming, tools |
| openrouter/writer/palmyra-x5 | openrouter | 1,040,000 | $0.633000 / $6.33 | passthrough | — | streaming, tools |
| openrouter/x-ai/grok-4.20 | openrouter | 2,000,000 | $1.318750 / $2.637500 | passthrough | — | streaming, tools |
| openrouter/x-ai/grok-4.20-multi-agent | openrouter | 2,000,000 | $1.318750 / $2.637500 | passthrough | — | streaming, tools |
| openrouter/x-ai/grok-4.3 | openrouter | 1,000,000 | $1.318750 / $2.637500 | passthrough | — | streaming, tools |
| openrouter/xiaomi/mimo-v2.5 | openrouter | 1,048,576 | $0.110775 / $0.2954 | passthrough | — | streaming, tools |
| openrouter/xiaomi/mimo-v2.5-pro | openrouter | 1,048,576 | $0.458925 / $0.917850 | passthrough | — | streaming, tools |
| openrouter/z-ai/glm-4.5 | openrouter | 131,072 | $0.633000 / $2.321 | passthrough | — | streaming, tools |
| openrouter/z-ai/glm-4.5-air | openrouter | 131,072 | $0.13715 / $0.896750 | passthrough | — | streaming, tools |
| openrouter/z-ai/glm-4.5v | openrouter | 65,536 | $0.633000 / $1.899000 | passthrough | — | streaming, tools |
| openrouter/z-ai/glm-4.6 | openrouter | 202,752 | $0.453650 / $1.835700 | passthrough | — | streaming, tools |
| openrouter/z-ai/glm-4.6v | openrouter | 131,072 | $0.316500 / $0.949500 | passthrough | — | streaming, tools |
| openrouter/z-ai/glm-4.7 | openrouter | 202,752 | $0.422000 / $1.84625 | passthrough | — | streaming, tools |
| openrouter/z-ai/glm-4.7-flash | openrouter | 202,752 | $0.0633 / $0.422000 | passthrough | — | streaming, tools |
| openrouter/z-ai/glm-5 | openrouter | 202,752 | $0.633000 / $2.0256 | passthrough | — | streaming, tools |
| openrouter/z-ai/glm-5-turbo | openrouter | 262,144 | $1.266000 / $4.22 | passthrough | — | streaming, tools |
| openrouter/z-ai/glm-5.1 | openrouter | 202,752 | $1.019130 / $3.202980 | passthrough | — | streaming, tools |
| openrouter/z-ai/glm-5v-turbo | openrouter | 202,752 | $1.266000 / $4.22 | passthrough | — | streaming, tools |
| openrouter/~anthropic/claude-haiku-latest | openrouter | 200,000 | $1.055 / $5.275000 | passthrough | — | streaming, tools |
| openrouter/~anthropic/claude-opus-latest | openrouter | 1,000,000 | $5.275000 / $26.375 | passthrough | — | streaming, tools |
| openrouter/~anthropic/claude-sonnet-latest | openrouter | 1,000,000 | $2.11 / $10.550000 | passthrough | — | streaming, tools |
| openrouter/~google/gemini-flash-latest | openrouter | 1,048,576 | $1.5825 / $9.495 | passthrough | — | streaming, tools |
| openrouter/~google/gemini-pro-latest | openrouter | 1,048,576 | $2.11 / $12.66 | passthrough | — | streaming, tools |
| openrouter/~moonshotai/kimi-latest | openrouter | 262,144 | $0.6963 / $3.59755 | passthrough | — | streaming, tools |
| openrouter/~openai/gpt-latest | openrouter | 1,050,000 | $5.275000 / $31.65 | passthrough | — | streaming, tools |
| openrouter/~openai/gpt-mini-latest | openrouter | 400,000 | $0.79125 / $4.7475 | passthrough | — | streaming, tools |
| perplexity/perplexity/glm-5.3 | perplexity | 1,048,576 | $1.40 / $4.40 | passthrough | — | streaming, tools |
| perplexity/perplexity/glm-5.3-flash | perplexity | 1,048,576 | $0.15 / $0.50 | passthrough | — | streaming, tools |
| perplexity/perplexity/kimi-k3 | perplexity | 1,048,576 | $3.00 / $15.00 | passthrough | — | streaming, tools |
| perplexity/perplexity/nemotron-3-ultra-550b-a55b | perplexity | 262,144 | $0.25 / $2.50 | passthrough | — | streaming, tools |
| perplexity/perplexity/sonar | perplexity | 128,000 | $0.25 / $2.50 | passthrough | — | streaming |
| perplexity/pplx-embed-context-v1-0.6b | perplexity | 32,768 | $0.008 / $0.00 | passthrough | — | embedding |
| perplexity/pplx-embed-context-v1-4b | perplexity | 32,768 | $0.05 / $0.00 | passthrough | — | embedding |
| perplexity/pplx-embed-v1-0.6b | perplexity | 32,768 | $0.004 / $0.00 | passthrough | — | embedding |
| perplexity/pplx-embed-v1-4b | perplexity | 32,768 | $0.03 / $0.00 | passthrough | — | embedding |
| perplexity/sonar | perplexity | 128,000 | $1.00 / $1.00 | passthrough | — | streaming |
| perplexity/sonar-deep-research | perplexity | 128,000 | $2.00 / $8.00 | passthrough | — | streaming, tools |
| perplexity/sonar-pro | perplexity | 200,000 | $3.00 / $15.00 | passthrough | — | streaming |
| perplexity/sonar-pro-search | perplexity | 200,000 | $3.00 / $15.00 | passthrough | — | streaming, tools |
| perplexity/sonar-reasoning-pro | perplexity | 128,000 | $2.00 / $8.00 | passthrough | — | streaming, tools |
| together/meta-llama-3.3-70b | together | 128,000 | $1.04 / $1.04 | passthrough | — | streaming, tools |
| xai/grok-3-mini | xai | 128,000 | $0.30 / $0.50 | passthrough | — | streaming, tools |
| xai/grok-4 | xai | 256,000 | $3.00 / $15.00 | passthrough | — | streaming, tools, vision |
| xai/grok-4.1-fast | xai | 256,000 | $0.20 / $0.50 | passthrough | — | streaming, tools, vision |
| xai/grok-4.20 | xai | 2,000,000 | $1.25 / $2.50 | passthrough | — | streaming, tools, vision |
| xai/grok-4.20-0309-non-reasoning | xai | 1,000,000 | $1.25 / $2.50 | passthrough | — | streaming, tools, vision |
| xai/grok-4.20-0309-reasoning | xai | 1,000,000 | $1.25 / $2.50 | passthrough | — | streaming, tools, vision |
| xai/grok-4.20-multi-agent | xai | 2,000,000 | $1.25 / $2.50 | passthrough | — | streaming, tools, vision |
| xai/grok-4.20-multi-agent-0309 | xai | 1,000,000 | $1.25 / $2.50 | passthrough | — | streaming, tools, vision |
| xai/grok-4.3 | xai | 1,000,000 | $1.25 / $2.50 | passthrough | — | streaming, tools, vision |
| xai/grok-4.5 | xai | 256,000 | $2.00 / $6.00 | passthrough | — | streaming, tools, vision |
| xai/grok-4.6 | xai | 500,000 | $2.00 / $6.00 | passthrough | — | streaming, tools, vision |
| xai/grok-4.7 | xai | 500,000 | $1.600000 / $4.80 | passthrough | — | streaming, tools |
| xai/grok-build-0.1 | xai | 256,000 | $1.00 / $2.00 | passthrough | — | streaming, tools |
# Models Billing & Metering
Source: https://www.plungeai.com/docs/models/billing
Planned: not available yet. Tracked as TI-74.
## What this will do
Account status, price = cost × org markup, reserve-then-settle, spend caps. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Models Streaming
Source: https://www.plungeai.com/docs/models/streaming
Planned: not available yet. Tracked as TI-74.
## What this will do
SSE chat completions, translated for non-OpenAI providers. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Workflows Quickstart
Source: https://www.plungeai.com/docs/workflows/quickstart
Planned: not available yet. Tracked as TI-74.
## What this will do
Run multi-agent CNL workflows inline or saved, sync or streamed. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Workflow Best Practices
Source: https://www.plungeai.com/docs/workflows/best-practices
Planned: not available yet. Tracked as TI-74.
## What this will do
Write CNL that validates, runs, and fails loudly. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# The CNL workflow spec
Source: https://www.plungeai.com/docs/workflows/core-concepts/cnl
Planned: not available yet. Tracked as TI-74.
## What this will do
Workflow structure, task types and validation rules. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Choosing agents and models
Source: https://www.plungeai.com/docs/workflows/core-concepts/agents-and-models
Planned: not available yet. Tracked as TI-74.
## What this will do
Agent tiers, model pinning, and how much time parallel fan-out takes. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Workflow Runs Lifecycle
Source: https://www.plungeai.com/docs/workflows/core-concepts/run-lifecycle
Planned: not available yet. Tracked as TI-74.
## What this will do
Create a run, read its status and result, cancel it. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Results and traces
Source: https://www.plungeai.com/docs/workflows/core-concepts/results-and-traces
Planned: not available yet. Tracked as TI-74.
## What this will do
What a run returns and how to read its persisted trace. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Follow-ups and continuation
Source: https://www.plungeai.com/docs/workflows/core-concepts/follow-ups
Planned: not available yet. Tracked as TI-74.
## What this will do
Chain a run with follow-ups and answer paused runs. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Interactive Chat
Source: https://www.plungeai.com/docs/workflows/examples/chat
Planned: not available yet. Tracked as TI-74.
## What this will do
A multi-turn chat session over MCP. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Deep Research
Source: https://www.plungeai.com/docs/workflows/examples/deep-research
Planned: not available yet. Tracked as TI-74.
## What this will do
A goal-driven research workflow that answers with sources. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Batch Enrichment
Source: https://www.plungeai.com/docs/workflows/examples/enrichment
Planned: not available yet. Tracked as TI-74.
## What this will do
Enrich a list of items with a `type: batch` workflow, then run it to completion. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# Workflow Webhooks
Source: https://www.plungeai.com/docs/workflows/webhooks
Planned: not available yet. Tracked as TI-25.
## What this will do
PlungeAI will call a URL you register when a workflow run finishes, so you do not have to hold a stream open or poll. Nothing sends outbound webhooks today.
## Use this today
Follow a run live over SSE.
Start a run without waiting and fetch the result later.
# Parallel and batch tasks
Source: https://www.plungeai.com/docs/workflows/parallel-tasks
Planned: not available yet. Tracked as TI-74.
## What this will do
Fan out with `type: parallel` and run a per-item pipeline with `type: batch`. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill reference that covers the same ground.
# Generate a workflow from a goal
Source: https://www.plungeai.com/docs/workflows/generate
Planned: not available yet. Tracked as TI-74.
## What this will do
`plungeai_build_workflow` turns a goal into CNL YAML. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Workflow streaming events
Source: https://www.plungeai.com/docs/workflows/streaming-events
Planned: not available yet. Tracked as TI-74.
## What this will do
SSE engine frames from `execute-stream`. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# MCP servers inside workflows
Source: https://www.plungeai.com/docs/workflows/mcp-tool-calling
Planned: not available yet. Tracked as TI-74.
## What this will do
Give a harness task MCP servers to call. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Saved workflows and versions
Source: https://www.plungeai.com/docs/workflows/saved-workflows
Planned: not available yet. Tracked as TI-74.
## What this will do
Save, version and run workflows by id. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Agents Quickstart
Source: https://www.plungeai.com/docs/agents/quickstart
Planned: not available yet. Tracked as TI-74.
## What this will do
Execute a registry agent from a prompt: sync, async or streamed. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Conversations and personas
Source: https://www.plungeai.com/docs/agents/features/conversations
Planned: not available yet. Tracked as TI-74.
## What this will do
Carry context with `messages[]` (partial: `prompt` stays required and threading depends on the agent), `persona`, and MCP `session_id` for reliable threading. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Output formats
Source: https://www.plungeai.com/docs/agents/features/output-formats
Planned: not available yet. Tracked as TI-74.
## What this will do
JSON, YAML, Markdown or text via `format` and `Accept`. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Agent streaming
Source: https://www.plungeai.com/docs/agents/features/streaming
Planned: not available yet. Tracked as TI-74.
## What this will do
OpenAI-shaped chunks with keepalives and `X-Execution-Id`. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Citations
Source: https://www.plungeai.com/docs/agents/features/citations
Planned: not available yet. Tracked as TI-76.
## What this will do
Agent answers will carry source annotations that tie each claim to the page or document it came from. Agent results have no citation field today.
## Use this today
A multi-step research workflow you can run today.
# Web search tool agents
Source: https://www.plungeai.com/docs/agents/features/web-search
Planned: not available yet. Tracked as TI-74.
## What this will do
Search the web through registry tool agents. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Outbound MCP runs
Source: https://www.plungeai.com/docs/agents/features/mcp-tools
Planned: not available yet. Tracked as TI-74.
## What this will do
Connect catalog MCP servers to a run and call their tools. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Async execution and results
Source: https://www.plungeai.com/docs/agents/features/async-results
Planned: not available yet. Tracked as TI-74.
## What this will do
`sync:false` returns 202; redeem the result later. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Direct requests
Source: https://www.plungeai.com/docs/agents/examples/direct-requests
Planned: not available yet. Tracked as TI-74.
## What this will do
Ask a question, ask for JSON, send YAML and get Markdown. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Use an agent as a subagent tool
Source: https://www.plungeai.com/docs/agents/examples/subagent
Planned: not available yet. Tracked as TI-74.
## What this will do
Wrap an agent call as a tool in your own orchestrator. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# OpenAI compatibility (agents plane)
Source: https://www.plungeai.com/docs/agents/openai-compatibility
Planned: not available yet. Tracked as TI-74.
## What this will do
Which OpenAI request and stream fields the agents plane accepts. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Discovery Quickstart
Source: https://www.plungeai.com/docs/discovery/quickstart
Planned: not available yet. Tracked as TI-74.
## What this will do
Find agents, tools, models, skills and connectors before you call. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Recommend
Source: https://www.plungeai.com/docs/discovery/recommend
Planned: not available yet. Tracked as TI-74.
## What this will do
Ask the registry which capability fits a task. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Search modes and fields
Source: https://www.plungeai.com/docs/discovery/core-concepts/search-modes
Planned: not available yet. Tracked as TI-74.
## What this will do
`mode` hybrid, keyword or vector; `fields` list, summary or full. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# The card object
Source: https://www.plungeai.com/docs/discovery/core-concepts/cards
Planned: not available yet. Tracked as TI-74.
## What this will do
What a registry card holds and how to read one. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Card status and kinds
Source: https://www.plungeai.com/docs/discovery/core-concepts/card-status
Planned: not available yet. Tracked as TI-74.
## What this will do
Only `status:active` cards run; kinds you can filter on. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Inspect a tool contract
Source: https://www.plungeai.com/docs/discovery/features/tool-contract
Planned: not available yet. Tracked as TI-74.
## What this will do
Read operations, required params and examples before executing. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Quality signals
Source: https://www.plungeai.com/docs/discovery/features/quality
Planned: not available yet. Tracked as TI-74.
## What this will do
Add `include=quality` to search results. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Catalog resources over MCP
Source: https://www.plungeai.com/docs/discovery/features/resources
Planned: not available yet. Tracked as TI-74.
## What this will do
Dropped: discovery search is synchronous and has no event stream. Replacement: read catalog lists as MCP resources. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Registry kinds
Source: https://www.plungeai.com/docs/discovery/features/kinds
Planned: not available yet. Tracked as TI-74.
## What this will do
Dropped: there is no long-running discovery run to notify on. Replacement: the kinds you can search. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Pagination
Source: https://www.plungeai.com/docs/discovery/features/pagination
Planned: not available yet. Tracked as TI-74.
## What this will do
Discovery search and `GET /v1/tools` page with `limit` and `offset` and return `count`; only `GET /v1/agents` returns `has_more` and `next_offset`. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Workflow templates
Source: https://www.plungeai.com/docs/discovery/features/templates
Planned: not available yet. Tracked as TI-74.
## What this will do
Dropped: a search cannot be cancelled. Replacement: start from a template. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Discovery first
Source: https://www.plungeai.com/docs/discovery/features/discovery-first
Planned: not available yet. Tracked as TI-74.
## What this will do
Dropped: there is no stateful FindAll-style run to refresh. Replacement: the discovery-first rule. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# Scheduling Quickstart
Source: https://www.plungeai.com/docs/scheduling/quickstart
Planned: not available yet. Tracked as TI-74.
## What this will do
Cron jobs for agents, queries, workflows and heartbeats. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Run now and run history
Source: https://www.plungeai.com/docs/scheduling/runs
Planned: not available yet. Tracked as TI-74.
## What this will do
Trigger a job now and read its run history. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Webhook triggers
Source: https://www.plungeai.com/docs/scheduling/webhook-triggers
Planned: not available yet. Tracked as TI-25.
## What this will do
You will be able to start a scheduled job or a workflow by calling a webhook URL on a public PlungeAI host. Inbound triggers are not available on a public host today.
## Use this today
Run a workflow on a cron schedule with the plungeai_schedule MCP tool.
# Heartbeat check
Source: https://www.plungeai.com/docs/scheduling/examples/heartbeat
Planned: not available yet. Tracked as TI-74.
## What this will do
A heartbeat job that checks a condition and acts on it. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Trigger a workflow or mission
Source: https://www.plungeai.com/docs/scheduling/examples/trigger-workflow
Planned: not available yet. Tracked as TI-74.
## What this will do
Run a workflow or mission on a schedule and notify a channel. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Scheduling: MCP tool → REST API
Source: https://www.plungeai.com/docs/scheduling/migration-guide
Planned: not available yet. Tracked as TI-24.
## What this will do
When a REST schedules API exists, this guide will map each `plungeai_schedule` action to its REST route so existing jobs keep running. Scheduling is available only through the MCP tool today.
## Use this today
Create and manage schedules with plungeai_schedule on `https://mcp.plungeai.com/v1`.
# Tools Quickstart
Source: https://www.plungeai.com/docs/tools/quickstart
Planned: not available yet. Tracked as TI-74.
## What this will do
List tool agents, read a contract, execute one operation. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Outcomes and remediation
Source: https://www.plungeai.com/docs/tools/outcomes
Planned: not available yet. Tracked as TI-74.
## What this will do
The seven outcomes, the remediation actions, and their HTTP statuses. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Tool catalog by category
Source: https://www.plungeai.com/docs/tools/catalog
Planned: not available yet. Tracked as TI-74.
## What this will do
The tool agents available today, by category. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Long runs (async)
Source: https://www.plungeai.com/docs/tools/async
Planned: not available yet. Tracked as TI-74.
## What this will do
`mode: "async"` for tool calls that take longer than one request. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Missions Quickstart
Source: https://www.plungeai.com/docs/missions/quickstart
Planned: not available yet. Tracked as TI-74.
## What this will do
Run a bounded agent loop with a goal, allowed tools and an iteration cap. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Mission bounds and the tool fence
Source: https://www.plungeai.com/docs/missions/bounds
Planned: not available yet. Tracked as TI-74.
## What this will do
What a mission may do and where it stops. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Skills, plugins and capability injection
Source: https://www.plungeai.com/docs/missions/capability-injection
Planned: not available yet. Tracked as TI-74.
## What this will do
Inject skills, experts, persona, backgrounds, plugins and MCP. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Pre-built agent cards
Source: https://www.plungeai.com/docs/missions/prebuilt-agents
Planned: not available yet. Tracked as TI-74.
## What this will do
Run a catalog agent card with `mission_ref` or `pack`. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Async missions: the Ten-Minute Relay
Source: https://www.plungeai.com/docs/missions/async-relay
Planned: not available yet. Tracked as TI-74.
## What this will do
Long missions run in resumable legs; poll for the result. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Connectors and connected accounts
Source: https://www.plungeai.com/docs/connectors/quickstart
Planned: not available yet. Tracked as TI-74.
## What this will do
OAuth and API-key connectors, and how a connected account reaches a call. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# The connector proxy
Source: https://www.plungeai.com/docs/connectors/proxy
Planned: not available yet. Tracked as TI-74.
## What this will do
Call a provider's API raw through `api.plungeai.com/v1/connect` with your `ozk_` key (`$PLUNGE_API_KEY`). This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Traces Quickstart
Source: https://www.plungeai.com/docs/traces/quickstart
Planned: not available yet. Tracked as TI-74.
## What this will do
Read the spans and gateway log of any run by trace id. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Execution history and cost
Source: https://www.plungeai.com/docs/traces/history-and-cost
Planned: not available yet. Tracked as TI-74.
## What this will do
List past runs and what they cost. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# The activity log
Source: https://www.plungeai.com/docs/traces/activity-log
Planned: not available yet. Tracked as TI-74.
## What this will do
Your key's call log on the MCP server. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# One Agent
Source: https://www.plungeai.com/docs/one-agent/overview
One Agent (`one-agent`) is the platform's agent runtime. It is one agent id with two modes, and the task decides which one runs. Saved bots run on it too: a bot is a saved workflow whose single task is a One Agent loop (see [Bots](/bots/overview)).
| Mode | Runs when | What you get | Page |
|---|---|---|---|
| Call mode | The task carries no loop command | One model answer: any provider and model, `persona`, `skill`, conversation history, streaming, `format` | [Call mode](/one-agent/call-mode) |
| Loop mode | The task carries a loop command | A bounded agentic run: tools inside a fence, a turn cap, memory recall, sub-agents, virtual files, approval gates | [Loop mode](/one-agent/loop-mode) |
## The mode rule
A task loops only when it carries a loop command:
- a turn count: `maxTurns`, `max_turns` or `max_iterations` of 1 or more, or
- a `type: harness` task, which the engine marks as a mission run.
`tools` and `effort` are not loop commands. With no loop command the task is answered once, in call mode.
A continue (`session_id` plus a new `prompt`, no loop command) resumes the session in the mode it started in.
## Reach it from each door
| Door | Call | Mode |
|---|---|---|
| REST agents plane | `POST /v1/agents/one-agent/execute` | Call. The loop fields (`goal`, `mission`, `allowed_tools`) are ignored on this plane. |
| REST workflows plane | `POST /v1/workflows/execute`, or a saved workflow, with a `type: harness` task and `agent: one-agent` | Loop |
| MCP | `plungeai_execute_agent` with `agent: one-agent` | Call |
| MCP | `plungeai_run_mission` (the loop runtime defaults to `one-agent`; async by default) | Loop |
| CNL | `agent: one-agent` in a `type: task`, `type: harness`, `parallel`, `sequential`, `debate`, `validate` or batch task | Call, or loop with a turn count or `type: harness` |
In a `type: harness` task the engine marks the run as a mission, so it always loops. The task's `allowed_tools` and `max_iterations` are the fence and the cap.
## Models and keys
Every model call goes through the provider layer. Loop mode needs a provider that returns tool calls: anthropic, openai, gemini, groq, xai, openrouter, cerebras, cohere, deepinfra, vercel and the other OpenAI-compatible ones. A loop on any other provider is refused with `outcome: needs_input`.
The platform key is the default. A customer who saved their own key for that provider uses it instead.
## Next steps
Single answers: fields, personas, skills, conversation history.
Bounded agentic runs: the loop command, the fence, sessions and stop reasons.
Every tool a loop can call, and which ones need a saved bot.
How a tool call becomes allow, ask or deny.
# One Agent call mode
Source: https://www.plungeai.com/docs/one-agent/call-mode
Call mode is what `one-agent` does when a task says nothing about looping. It answers once with the contract of `llm-agent`: any provider and model, a persona or skill, conversation history, streaming, and a choice of response format.
## A first call
```bash
curl -s -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"prompt":"In one sentence: what is a content delivery network?","sync":true,"max_tokens":60}'
```
The answer is in `content`. The request fields are listed on [Execute an agent](/api-reference/agents/execute-agent).
## Fields
| Field | Type | Meaning |
|---|---|---|
| `prompt` (alias `input`) | string | The question or instruction. Required unless `messages` has a user turn. |
| `messages` | array | Prior chat turns `[{role, content}]`; the model answers from the whole conversation. |
| `system` | string | System prompt. |
| `provider`, `model` | string | Pin a provider and model. Send them together; the model is checked against the catalog. The default provider is `gemini`. |
| `persona` | string | A persona id. It is the only capability field the agents plane forwards. |
| `max_tokens` | number | Output cap. |
| `temperature`, `top_p`, `reasoning_effort`, `thinking_level` | | Sampling and reasoning hints forwarded to the model. |
| `stream` | boolean | `true` returns an OpenAI-shaped stream that ends with `data: [DONE]`. |
| `sync` | boolean | Default `true`. `false` returns a `202` pointer; redeem it with `GET /v1/agents/results/{workflowId}/{taskId}`. |
| `format` | string | `json`, `yaml`, `markdown` or `text` (see [Four wires](/api-reference/four-wires)). |
| `user_context` | object | The caller's time zone, locale and place. Default: derived from the request's network location. |
A persona, skill or expert id is resolved by the same capability layer that serves bots and missions, so one id means the same thing everywhere.
## Conversation
- `messages` carries a conversation inside one call.
- In a workflow task, `memory: true` with a `memory_scope` loads the stored conversation history for that scope.
- A task with `session_id` and a new `prompt` continues a stored session. Over MCP, `plungeai_execute_agent` takes `session_id` and `plungeai_continue` answers a pause.
- A call with nothing to answer is refused with `needs_input` rather than sent to the model.
## In a workflow task
A `type: task` step with `agent: one-agent` takes the same fields, and also `skill` or `skills`, `expert` or `experts`, and `memory: true`. Send it to `POST /v1/workflows/execute` with `Content-Type: text/yaml`:
```yaml
workflow:
name: skill-demo
tasks:
- id: assess
type: task
agent: one-agent
prompt: "Assess a company with revenue 120M (+18% YoY), gross margin 72%, operating margin -4%, cash 80M, burn 15M per year. Three sentences."
skill: financial-analysis
maxTokens: 200
```
The answer comes back as `content` of the final task, in the wire you sent (YAML in, YAML out; see [Four wires](/api-reference/four-wires)).
## Answer as a persona
```bash
curl -s -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"prompt":"Index fund or individual stocks for a 30-year-old? Two sentences.","persona":"warren-buffett","max_tokens":120}'
```
## Need tools or more than one turn?
Add a loop command and the same id runs [loop mode](/one-agent/loop-mode).
# One Agent loop mode
Source: https://www.plungeai.com/docs/one-agent/loop-mode
Loop mode is a bounded agentic run: the model calls tools inside a fence, reads the results and goes again until it calls `task_complete` or a cap stops it. It is what a `type: harness` task with `agent: one-agent` runs, and it is what every [bot](/bots/overview) runs.
## Start a loop
A task loops when it carries a loop command: `max_turns` or `max_iterations` of 1 or more, or `type: harness`. Over REST that is a workflow on the workflows plane. This run is capped at 2 iterations and injects one skill:
```bash
curl -s https://api.plungeai.com/v1/workflows/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"workflow":{"name":"cap-demo","tasks":[{"type":"harness","id":"h1","agent":"one-agent","goal":"Reply with the single word pong, then call task_complete.","mission":"You are a terse assistant. Answer in one word.","allowed_tools":["task_complete"],"max_iterations":2,"skills":["report"]}]}}'
```
Over MCP, `plungeai_run_mission` takes a `goal`, an optional `mission`, `allowed_tools`, `max_iterations` (1 to 50), `success_criteria`, `persona` and `skills`; it runs on `one-agent` and answers async by default. Unlike a harness task, it has its own defaults: `allowed_tools` is `web_search`, `web_fetch` and `task_complete`, and `max_iterations` is 8.
## The task keys
Write them flat on the harness task. The engine folds them into the mission and forwards the bot keys as written.
| Key | Meaning |
|---|---|
| `goal` or `prompt` | What the run is for. |
| `mission` | The run's purpose text. A whitespace-free string is read as the id of a pre-built agent card instead. A custom `mission` replaces the harness's own system prompt, so keep an instruction to call `task_complete`. |
| `allowed_tools` | The fence. Fail-closed: a tool not listed never runs. Without it, a task on `agent: one-agent` has [every tool in the runtime](/one-agent/tools#without-allowed_tools). |
| `max_iterations` (alias `max_turns`) | The turn cap. `effort` (`quick`, `standard`, `deep`) sets a default cap when no number is given. |
| `success_criteria` | Added to the system prompt and self-checked by the agent; there is no separate verifier. |
| `skills`, `experts`, `persona`, `backgrounds`, `plugins`, `mcp` | Capabilities injected into the run. |
| `model`, `provider`, `effort`, `max_tokens` | Model choice. `effort` reaches the provider as `reasoning_effort` only when the caller set it. |
| `allowed_agents`, `denied_agents` | Which platform agents `call_agent` may reach. |
| `permissions`, `permission_locks` | Allow, ask or deny per tool or class. See [Policy and review](/one-agent/policy-and-review). |
| `budget_usd_run` | Per-run spend cap in USD. |
| `peers`, `posture`, `review`, `max_runtime_s`, `workspace` | The always-on bot keys. See the [playbook reference](/bots/playbook-reference). |
## Sessions and pauses
- A run that needs a person pauses. `ask_user` pauses on a question and a gated tool call pauses on an approval. Answer with `POST /v1/executions/{id}/continue` or MCP `plungeai_continue`.
- A running loop can be steered: `POST /v1/executions/{id}/steer` queues a message the loop reads at its next turn. A finished run answers `409`.
- A continue (`session_id` plus a new `prompt`) resumes the session in loop mode, with its stored state.
- Long runs continue in legs of about ten minutes. What must survive a leg or a continue (the denial streak, handed-off calls, the run's taint) rides the session.
## How a run stops
| Stop | Cause |
|---|---|
| `task_complete` | The model called it with the result. |
| Turn cap | `max_iterations` reached. |
| `stop_reason: budget` | The running token cost passed `budget_usd_run`. |
| `stop_reason: timeout` | `max_runtime_s` elapsed, checked between turns. The run does not yield or resume. |
| `stop_reason: denied` | Three denied tool calls in a row on an unattended run. An interactive run pauses on a question instead. |
| `stop_reason: review` | The reviewer denied three calls in a row. |
| Cancel | `POST /v1/executions/{id}/cancel`. It also cancels the child runs the loop started. |
A loop on a provider that cannot return tool calls is refused with `outcome: needs_input` before it starts.
## Sub-agents and files
- `delegate` runs focused sub-agents in parallel, each with only the tools you grant it. A child with no `agent` or `mission_ref` runs on `one-agent`.
- The file tools (`read_file`, `write_file`, `edit_file`, `list_files`, `search_files`, `delete_file`) work on a virtual workspace. `workspace: run` starts it clean each run; `workspace: bot` keeps it across runs. See [Workspace and inbox](/bots/workspace-and-inbox).
- `recall` and `recall_history` search your own past runs; `memory` keeps durable facts. See [Tools](/one-agent/tools).
# One Agent tools
Source: https://www.plungeai.com/docs/one-agent/tools
A loop can only call the tools its fence lists. The fence is `allowed_tools` on the harness task; a tool not listed never runs. This page lists what exists. How a call is allowed, asked or denied is on [Policy and review](/one-agent/policy-and-review).
## Without `allowed_tools`
On `agent: one-agent`, a task that names no `allowed_tools` gets every tool on this page. Name `allowed_tools` to narrow it. `universal-agent`, the concierge runtime, has a smaller default fence: the core eight below.
## The core eight
| Tool | Does |
|---|---|
| `registry_search` (alias `find_agent`) | Searches the platform registry for agents, workflows, connectors and capability cards. |
| `registry_lookup` (alias `agent_card`) | Reads one card in full: parameters, prompt format, examples. |
| `call_agent` (alias `run_agent`) | Runs a platform agent by id and operation. |
| `web_search` | Web search. |
| `web_fetch` | Fetches a public URL. It follows up to 5 redirects by hand, checks every hop is public, and refuses https to http. |
| `load_skill` | Loads a skill, persona, expert or background that was not already injected. |
| `ask_user` | Pauses the run on a question for the person. |
| `task_complete` | Ends the run with the result. |
A fence that lists `allowed_tools` must include `task_complete`, or the run cannot finish cleanly.
## The other tools
| Tool | Does |
|---|---|
| `read_file`, `write_file`, `edit_file`, `list_files`, `search_files`, `delete_file` | A virtual workspace. `workspace: bot` keeps it across runs. |
| `memory` | Durable facts that survive across sessions. One call applies a batch of `operations` (`add`, `replace`, `remove`) to the `user` or `memory` target. `read` and `forget {query}` are single calls of their own; `forget` purges every entry containing the text. |
| `knowledge` | The user's knowledge base: `query`, `get_page`, `list_pages`, `save`, `add_source`, `ingest`, `lint`. |
| `recall`, `recall_history` | Search your own past runs and reuse a prior result instead of redoing the work. |
| `skill_manage` | Create, patch or delete a private skill. Never on by default; every write pauses for approval unless `permissions: { skill_manage: allow }`. |
| `delegate` | Runs focused sub-agents in parallel, each with only the tools you grant it. |
| `invoke_workflow` | Runs a CNL workflow, from a template id or inline YAML. |
| `run_python` | Runs Python for calculations and data transforms. |
| `local_agent` | Runs an action on a local agent on the owner's machine, over the tunnel. |
| `platform_action` | Asks the user's client to perform a platform action; the run pauses until the client resumes it. |
| `mcp_tool_search` | Loads MCP tool schemas on demand (see below). |
### MCP tools
MCP tools reach the loop under names that start with `mcp__`. Past 15 tools or 8 KB of schemas, the system prompt lists them by name only and `mcp_tool_search` loads up to 5 schemas for the next turn. A continue or a new leg forgets them: search again. The search never offers a tool the run's permissions or posture refuse.
## Bot tools
When you narrow the fence, list the ones you want in `allowed_tools`. `schedule`, `message_bot` and the `browser` bot session need a saved bot, because the engine stamps the bot id and owner on the run. `code` needs no saved bot, only a connected OceanCode.
| Tool | Input | Does |
|---|---|---|
| `schedule` | `action`: `create`, `list`, `pause`, `resume`, `delete`, `run_now`, `wake` | Manages the bot's own schedule. See [Schedules and wake](/bots/schedules-and-wake). |
| `message_bot` | `to`, `text`, `wait?` | Messages another bot, an A2A agent or a bot group. See [Bot to bot and groups](/bots/bot-to-bot-and-groups). |
| `browser` | `steps`, `url?`, `session?` (`bot` or `none`) | Drives a cloud browser. `session: bot` (the default for a saved bot) keeps the bot's cookies between runs. Screenshots are saved under `/shots/` in the bot's files. |
| `code` | `prompt`, `directory?`, `session_id?`, `timeout_s?` | Hands one coding turn to the owner's OceanCode on their machine, over the Local Tunnel. `timeout_s` defaults to and is capped at 300. Pass the returned `session_id` to continue. With no OceanCode connected the tool says so. |
## Permission classes
Every call has a class. The class is what `permissions`, `posture` and the stored presets match on.
| Tool or case | Class |
|---|---|
| A guarded money category | `pay` |
| `message_bot` | `send` |
| `code` | `write` |
| `schedule` | by action: `delete` is `delete`, `list` is `read`, the rest are `write` |
| `browser` | `write` when a step clicks, types, runs script or presses keys, otherwise `read` |
| `mcp__*` | from the server's own hints: destructive is `delete`, read-only is `read`, anything else `write` |
| `local_agent` | `write` for actions that change the machine, otherwise `read` |
# One Agent policy and review
Source: https://www.plungeai.com/docs/one-agent/policy-and-review
Every tool call in a loop is classified, then decided. One function makes the decision, so every door (Studio, REST, MCP, the CLI, the scheduler) gets the same answer, and the rung that decided is written to the run's audit row.
## Classes and presets
A call has one of five classes: `read`, `write`, `send`, `delete` or `pay` (how each tool gets its class is on [Tools](/one-agent/tools#permission-classes)). The `permissions` map on a task, and the stored presets on a saved bot, give a class or a tool name one preset:
| Preset | Effect |
|---|---|
| `allow` | The call runs. |
| `ask` | The run pauses for approval. |
| `deny` | The call does not run; the model gets an error that names the reason. |
| `hand_off` | A `deny` that does not retry. The call never runs, and the run's result carries a `[handed off to you — not run: …]` line so the owner knows it is theirs to do. A hand-off does not count toward the denial breaker. |
A preset this build does not recognise is treated as `ask`, never `allow`. `hand_off` is understood only by One Agent; for any other agent the engine rewrites it to `deny`.
## The decision order
The first rung that applies decides:
1. **Posture `read_only`:** `write`, `send`, `delete` and `pay` are denied. So are `delegate`, `invoke_workflow` and every MCP tool, and any tool with an unknown class (fail closed).
2. **Explicit `deny`:** on the tool's name or on its class. Posture adds a class `deny` on every mutating class.
3. **`hand_off`.**
4. **The money floor:** an operation in the always-gated set (`buy`, `purchase`, `pay`, `transfer`, `send`, `send_payment`, `withdraw`, `fetch_paid`, `shop`) is always `ask`. No `allow` lowers it, and it pauses the run even when unattended.
5. **The preset:** the tool's own entry, then its class entry. Anything other than `allow` asks.
6. **An admin lock:** a locked class lifts `allow` to `ask`. It never lifts a `deny`.
7. **Otherwise `allow`.**
The presets on the task and the presets stored on the saved bot are merged before the run starts: the task's own entries win per class, the bot's locks replace what the YAML carried, and the lower per-run budget wins. See [Permissions and budget](/bots/permissions-and-budget).
## Posture
`posture: read_only` makes a run safe to point at the open web. A bot's own `permissions` cannot lift it, and an admin lock never turns a deny into an allow. The `browser` tool still reads pages under it. Any value other than `read_only` is forced to `read_only`: a typo never widens a run.
## Review
`review: auto` adds one model call that reviews each `write`, `send`, `pay` or `delete` call the permissions allowed. It answers allow, ask the owner (the normal approval pause) or deny (a tool error that names the reason). A reviewer error asks and never allows. Three denials in a row stop the run with `stop_reason: review`. It costs one model call per gated action. Any other value is forced to `auto`.
## The denial breaker
Three denied calls in a row, with no executed call between them, stop an unattended run with `stop_reason: denied`. An interactive run pauses and asks the person how to proceed. Any executed call resets the count.
## Secrets stay out of results
Tool results, spilled files, the outcome of an approval and the saved result pass through a redactor first. JWTs, bearer tokens and `sk-` or `ozk_` keys become `[REDACTED:]`. Card numbers, IBANs and email addresses pass, because they are the owner's own data.
## Untrusted content
Text someone else wrote is data, not instructions. The results of `web_search`, `web_fetch`, `browser`, `message_bot`, every `mcp__*` tool and `call_agent` calls to search, MCP or scraping agents are scanned for instruction patterns. A result that matches is wrapped in an `` marker listing the patterns it carries; a clean result is returned as it is. Either way the run is marked tainted. Today the taint is recorded in the audit trail only: a later `write` or `send` call writes a row saying it would have been asked, and nothing is blocked because of it. `code` results are not treated as untrusted.
`web_fetch` refuses private and link-local addresses.
## Other run limits
| Limit | Key | Stops with |
|---|---|---|
| Spend per run | `budget_usd_run` | `stop_reason: budget` |
| Wall-clock time | `max_runtime_s` | `stop_reason: timeout` |
| Turns | `max_iterations` | the turn cap |
Cancelling a run also cancels the child runs it started with `delegate` or `message_bot wait: true`, and the OceanCode sessions it started with `code`.
# Bots
Source: https://www.plungeai.com/docs/bots/overview
A bot is a saved workflow with `kind: bot`. Its one task is a [One Agent loop](/one-agent/loop-mode) with a mission. It is not a separate runtime: what makes it a bot is the wiring around the YAML, which is where the schedule, the chats, the triggers, the policy and the budget live.
| A bot has | Set by | Page |
|---|---|---|
| A mission and a tool fence | The YAML | [Playbook reference](/bots/playbook-reference) |
| A clock | `schedule` tool, a schedule on the bot | [Schedules and wake](/bots/schedules-and-wake) |
| Teammates | `peers`, bot groups, A2A | [Bot to bot and groups](/bots/bot-to-bot-and-groups) |
| Chats and a web widget | Pairing and bindings | [Channels and voice](/bots/channels-and-voice) |
| Rules and a spend cap | Stored presets, locks, budgets | [Permissions and budget](/bots/permissions-and-budget) |
| Files, an inbox and memory | `workspace`, `memory` | [Workspace and inbox](/bots/workspace-and-inbox) |
| Outside events | Webhook, email, Slack, Gmail, Git | [Triggers and hooks](/bots/triggers-and-hooks) |
| A control surface on every door | REST, MCP, CLI | [REST, MCP and CLI](/bots/rest-mcp-cli) |
## A minimal bot
```yaml
workflow:
name: Competitor Watch
description: Checks a page for pricing changes
tasks:
- type: harness
id: watch
agent: one-agent
goal: "Check {input} for pricing changes"
max_iterations: 8
allowed_tools: [web_search, web_fetch, task_complete]
mission: |
You are Competitor Watch. Reply NO-CHANGE when nothing changed.
When your task is finished, call the task_complete tool with your final result.
```
Save it as a bot over REST:
```bash
curl -s -X POST https://api.plungeai.com/v1/workflows \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"name":"Competitor Watch","kind":"bot","yaml":"workflow:\n name: Competitor Watch\n tasks:\n - type: harness\n id: watch\n agent: one-agent\n goal: \"Check {input} for pricing changes\"\n allowed_tools: [web_search, web_fetch, task_complete]\n mission: |\n You are Competitor Watch. Reply NO-CHANGE when nothing changed.\n When your task is finished, call the task_complete tool with your final result.\n"}'
```
In Studio, choose **New**, then **Bot agent**; the bot opens in the same YAML editor as a flow. The bot templates (`GET /v1/templates?kind=bot`) are ready-made starting points; `POST /v1/templates/{id}/use` copies one into your account.
## What a save checks
Saving runs the CNL schema and then the kind lint, before anything is written. A refusal is `422 invalid_workflow` with `errors` and `warnings`, and the stored row is unchanged.
- A bot is exactly one task, and it is `type: harness`.
- A declared `allowed_tools` fence must include `task_complete`. A mission that never mentions `task_complete` is a warning.
- `memory_owner` is refused: a bot's memory tenant comes from the user who runs it.
- A local bot (`local: true` or `local_agents`) should set `agent: one-agent`. `harness-agent` and `agentic-agent` are still accepted until the switch-over; `universal-agent` never is.
- `posture`, `peers`, `review`, `max_runtime_s` and `workspace` apply only on `agent: one-agent`. On `harness-agent`, `agentic-agent` or `universal-agent` they are an error, because those runtimes would ignore them.
- A hard-coded email address or UUID in the YAML is a warning.
## Identity and who may use what
When a saved bot runs, the engine tells the loop which bot it is (`bot_id`) and who owns it (`bot_owner`). The inbox, the `schedule` and `message_bot` tools, the `browser` bot session and the persistent workspace need that identity.
- These abilities belong to the owner. A teammate who runs a shared bot runs it without an identity.
- A saved workflow that is not a bot but uses `agent: one-agent`, run by its owner, is treated like a bot.
- Deleting a bot deletes its scheduled jobs and wipes its own memory namespace (the one a web widget uses). What the bot saved with `memory` while you ran it lives in your own memory and stays; remove it with `POST /v1/memory/forget`.
## Every run uses the stored policy
The engine loads the bot's stored policy once per run, on every door: a run by id, a schedule, a trigger, a chat message, MCP and Studio all stop at the same caps and holds. See [Permissions and budget](/bots/permissions-and-budget).
# Bot playbook reference
Source: https://www.plungeai.com/docs/bots/playbook-reference
Every key a One Agent bot reads, one row each. This page is generated from the playbook reference template, `scripts/bot-templates/bot-playbook-reference.yaml`, so the table and the template cannot disagree. Copy the lines you need into the harness task of a bot and delete the rest; the order of keys does not matter.
The keys `posture`, `peers`, `review`, `max_runtime_s` and `workspace` apply only on `agent: one-agent`; saving them on another loop runtime is refused. See [Bots](/bots/overview#what-a-save-checks).
## Doors
| Door | What it is |
|---|---|
| YAML | Any door that saves the bot's YAML: Studio's Code tab, `POST /v1/workflows` and `PATCH /v1/workflows/{id}`, MCP `plungeai_workflow` `create` and `update`, `ocean-cli workflows create` and `update`. |
| Studio | The `bot-config` block of Studio's Think tab, which writes these keys into the YAML for you; the bot's Settings panel edits some of them too. |
| Stored policy | The permission presets and the spend cap stored on the saved bot: `PUT /v1/workflows/{id}/permissions`, MCP `permissions_set`, `ocean-cli workflows permissions set`, and Studio's Permissions and Budget fields. They are merged with the YAML on every run ([Permissions and budget](/bots/permissions-and-budget)). |
## Keys
| Key | Type | Default | What it does | Set by |
|---|---|---|---|---|
| `type` | string | none, required | A bot is exactly one task of type harness (a mission-bounded loop run). | YAML |
| `id` | string | none, required | The task id; the run's result is stored under it. | YAML |
| `agent` | string | harness-agent on a task with no agent line | The runtime that runs the loop; only one-agent reads the bot keys below. | YAML |
| `goal` | string | none, required | What this run is asked to do; `{input}` is replaced by the run's input. | YAML |
| `mission` | string | none, required | The standing instructions (system prompt); end it by telling the bot to call `task_complete`. | YAML |
| `workspace` | string | bot for a saved bot | `bot` keeps the bot's virtual files across runs; run starts every run clean. | YAML, Studio |
| `posture` | string | unset (not read-only) | `read_only` denies every write, send, delete and pay (and delegate, `invoke_workflow`, MCP tools) whatever permissions says. | YAML, Studio |
| `peers` | list | none (no 1:1 messaging) | The only bots, or `a2a:` agents, that `message_bot` may reach by name or id; a `group:` send needs no peers. | YAML, Studio |
| `review` | string | unset (no review) | `auto` has a model check each allowed write, send, delete or pay call before it runs (one extra call each). | YAML, Studio |
| `max_runtime_s` | number | unset (no cap beyond the turn cap) | Wall-clock cap in seconds for the whole run; the run stops with `stop_reason` timeout. | YAML, Studio |
| `budget_usd_run` | number | unset (no cap); a lower cap stored on the saved bot wins | Per-run spend cap in USD, above 0 (blank = no cap; 0 is refused by the YAML block and Settings, and the runtime ignores a non-positive stored value); the loop stops at the next turn once cost passes it. | YAML, Stored policy |
| `permissions` | map | allow, except the money floor which always asks | Per-class or per-tool presets for send, write, pay and delete; reads are allowed unless you name `read` or the tool. | YAML, Stored policy |
| `permissions.send` | string | allow, but a registry-agent call with operation send is always gated (ALWAYS_GATED) even with send: allow | Preset for outward messages (email, chat, `message_bot`); ask pauses the run for the owner's approval. | YAML, Stored policy |
| `permissions.write` | string | allow | Preset for calls that change something (files, schedule create, browser clicks). | YAML, Stored policy |
| `permissions.pay` | string | allow, but payment ops always ask | `hand_off` never runs the call and tells the owner it is theirs to do; the result ends with a `[handed off to you]` line. | YAML, Stored policy |
| `permissions.delete` | string | allow | Preset for destructive calls; deny refuses them. | YAML, Stored policy |
| `allowed_tools` | list | every tool of the runtime on agent one-agent (universal-agent's default is the 8-tool DEFAULT_FENCE); list tools to narrow it | The tool fence; only these tools reach the model and `task_complete` must be in it. | YAML, Studio |
| `allowed_tools.schedule` | tool name | available when `allowed_tools` is absent; name it to keep it in a narrowed fence | The bot schedules itself (create, list, pause, resume, delete, `run_now`, wake); needs a saved bot. | YAML, Studio |
| `allowed_tools.message_bot` | tool name | available when `allowed_tools` is absent; name it to keep it in a narrowed fence | Sends to a peer or `group:` (async by default, replies arrive as `[MESSAGE from ...]` in a later run); needs a saved bot. | YAML, Studio |
| `allowed_tools.browser` | tool name | available when `allowed_tools` is absent; name it to keep it in a narrowed fence | Drives a cloud browser in one session, with the bot's saved login (click and type count as writes). | YAML, Studio |
| `allowed_tools.memory` | tool name | available when `allowed_tools` is absent; name it to keep it in a narrowed fence | Add, replace, remove, read, or forget `{query}` to purge matching entries. | YAML, Studio |
| `allowed_tools.code` | tool name | available when `allowed_tools` is absent; name it to keep it in a narrowed fence | Hands one turn (max 300 s) to the owner's OceanCode over the Local Tunnel; no daemon connected means no tool. | YAML, Studio |
| `allowed_agents` | list | all except payment agents | The registry agents `call_agent` may reach (payment agents only when named). | YAML |
| `skills` | list | none | Registry skill ids injected into the system prompt, at most 5. | YAML, Studio |
| `persona` | string | none | One persona id that sets the voice. | YAML, Studio |
| `max_turns` | number | the effort preset's cap, else the runtime's own | Turn cap for the loop (`max_iterations` is the legacy spelling). | YAML, Studio |
| `effort` | string | unset | `quick`, standard or deep sets the default turn cap and how wide the bot works. | YAML |
## The whole file
```yaml
# Playbook reference: every key a one-agent bot reads, set once, with one comment line above it.
# Each comment reads "what it does. Default: what you get when the key is absent."
# Copy the lines you need and delete the rest. Order in a real bot does not matter.
workflow:
name: "Playbook Reference"
description: "Every one-agent bot key, set and explained in comments. Copy the lines you need."
tasks:
# type: a bot is exactly one task of type harness (a mission-bounded loop run). Default: none, required.
- type: harness
# id: the task id; the run's result is stored under it. Default: none, required.
id: reference
# agent: the runtime that runs the loop; only one-agent reads the bot keys below. Default: harness-agent on a task with no agent line.
agent: one-agent
# goal: what this run is asked to do; {input} is replaced by the run's input. Default: none, required.
goal: "{input}"
# mission: the standing instructions (system prompt); end it by telling the bot to call task_complete. Default: none, required.
mission: |
You are Playbook Reference, a bot that exists to show every bot key in its own YAML.
What each key does is written as a comment in that YAML, which you cannot see, so
never explain a key from memory. Reply with REFERENCE-OK, a one-line echo of the
goal, and the sentence "Read the comment above each key in this playbook."
Your persona only sets the voice; this reply format always wins.
When your task is finished, call the task_complete tool with your reply.
# workspace: bot keeps the bot's virtual files across runs; run starts every run clean. Default: bot for a saved bot.
workspace: bot
# posture: read_only denies every write, send, delete and pay (and delegate, invoke_workflow, MCP tools) whatever permissions says. Default: unset (not read-only).
posture: read_only
# peers: the only bots, or "a2a:" agents, that message_bot may reach by name or id; a group: send needs no peers. Default: none (no 1:1 messaging).
peers: ["Playbook Peer"]
# review: auto has a model check each allowed write, send, delete or pay call before it runs (one extra call each). Default: unset (no review).
review: auto
# max_runtime_s: wall-clock cap in seconds for the whole run; the run stops with stop_reason timeout. Default: unset (no cap beyond the turn cap).
max_runtime_s: 300
# budget_usd_run: per-run spend cap in USD, above 0 (blank = no cap; 0 is refused by the YAML block and Settings, and the runtime ignores a non-positive stored value); the loop stops at the next turn once cost passes it. Default: unset (no cap); a lower cap stored on the saved bot wins.
budget_usd_run: 1
# permissions: per-class or per-tool presets for send, write, pay and delete; reads are allowed unless you name `read` or the tool. Default: allow, except the money floor which always asks.
permissions:
# permissions.send: preset for outward messages (email, chat, message_bot); ask pauses the run for the owner's approval. Default: allow, but a registry-agent call with operation send is always gated (ALWAYS_GATED) even with send: allow.
send: ask
# permissions.write: preset for calls that change something (files, schedule create, browser clicks). Default: allow.
write: allow
# permissions.pay: hand_off never runs the call and tells the owner it is theirs to do; the result ends with a [handed off to you] line. Default: allow, but payment ops always ask.
pay: hand_off
# permissions.delete: preset for destructive calls; deny refuses them. Default: allow.
delete: deny
# allowed_tools: the tool fence; only these tools reach the model and task_complete must be in it. Default: every tool of the runtime on agent one-agent (universal-agent's default is the 8-tool DEFAULT_FENCE); list tools to narrow it.
allowed_tools: [web_search, web_fetch, memory, schedule, message_bot, browser, code, task_complete]
# allowed_tools.schedule: the bot schedules itself (create, list, pause, resume, delete, run_now, wake); needs a saved bot. Default: available when allowed_tools is absent; name it to keep it in a narrowed fence.
# allowed_tools.message_bot: sends to a peer or group: (async by default, replies arrive as [MESSAGE from ...] in a later run); needs a saved bot. Default: available when allowed_tools is absent; name it to keep it in a narrowed fence.
# allowed_tools.browser: drives a cloud browser in one session, with the bot's saved login (click and type count as writes). Default: available when allowed_tools is absent; name it to keep it in a narrowed fence.
# allowed_tools.memory: add, replace, remove, read, or forget {query} to purge matching entries. Default: available when allowed_tools is absent; name it to keep it in a narrowed fence.
# allowed_tools.code: hands one turn (max 300 s) to the owner's OceanCode over the Local Tunnel; no daemon connected means no tool. Default: available when allowed_tools is absent; name it to keep it in a narrowed fence.
# allowed_agents: the registry agents call_agent may reach (payment agents only when named). Default: all except payment agents.
allowed_agents: [google-gmail]
# skills: registry skill ids injected into the system prompt, at most 5. Default: none.
skills: [web-search]
# persona: one persona id that sets the voice. Default: none.
persona: richard-feynman
# max_turns: turn cap for the loop (max_iterations is the legacy spelling). Default: the effort preset's cap, else the runtime's own.
max_turns: 8
# effort: quick, standard or deep sets the default turn cap and how wide the bot works. Default: unset.
effort: quick
```
# Bot schedules and wake
Source: https://www.plungeai.com/docs/bots/schedules-and-wake
A bot runs when something starts it: you, a trigger, a chat message, or a schedule. A schedule is a scheduler job that runs the bot's saved workflow. You can create one from outside, or let the bot create its own.
## Schedule a bot from outside
Over REST, `POST /v1/schedules` takes a saved workflow or bot id and either a cron expression or `@once` with `run_at`:
```bash
curl -s -X POST https://api.plungeai.com/v1/schedules \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"name":"Morning digest","workflow_id":"","schedule":"0 9 * * 1-5","timezone":"America/Los_Angeles","parameters":{"deliver":["slack"]}}'
```
A one-time run removes itself after it fires:
```bash
curl -s -X POST https://api.plungeai.com/v1/schedules \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"name":"One-off report","workflow_id":"","schedule":"@once","run_at":"2026-10-06T09:00:00Z"}'
```
| Field | Meaning |
|---|---|
| `schedule` | A cron expression, or `@once` together with `run_at`. |
| `run_at` | ISO-8601 time with a UTC offset, in the future. Required for `@once`. Refused with `run_at_required` or `run_at_past` otherwise. |
| `timezone` | The IANA zone the cron is read in. Default UTC. |
| `ends_at` | ISO-8601 time or ms epoch after which the schedule stops. `PATCH` with `ends_at: null` clears it. |
| `parameters.deliver` | Where each result goes (below). |
| `parameters.input` | The per-run input. |
The same operations are on MCP (`plungeai_schedule`) and the CLI (`schedules create`, `--cron @once --at `, `--ends-at`); see [REST, MCP and CLI](/bots/rest-mcp-cli).
## Let the bot schedule itself
List `schedule` in the bot's `allowed_tools`. The tool works on the bot's own jobs only:
| Action | Input | Does |
|---|---|---|
| `create` | `name`, `cron`, `timezone?`, `prompt?`, `deliver?`, `ends_at?` | A recurring job. The model writes the 5-field cron and the reply echoes the schedule in words, so you can check it said what you asked. |
| `create` with `cron: "@once"` | `run_at` | One run at a clock time. Refused without a UTC offset. |
| `wake` | `in`, `note?` | One later run after a delay (`10m`, `2h`, `1d`; at least 60 s). The note becomes the run's input. |
| `list` | | The bot's jobs with ids, the schedule in words and the next run. |
| `pause`, `resume`, `delete` | `id` | By job id. |
| `run_now` | `id`, `input?` | Fires a job once, now. |
A bot holds at most 50 active jobs. `ends_at` retires a job after that time. With no `timezone` the job uses the user's zone, otherwise UTC.
The class of a `schedule` call is by action: `delete` is `delete`, `list` is `read`, the rest are `write`. Under `posture: read_only` only `list` runs.
## Wake
A wake re-runs the bot once, later, without touching its schedule. The bot calls `schedule` with `action: wake`; from outside, `POST /v1/schedules/{id}/wake` does the same:
```bash
curl -s -X POST https://api.plungeai.com/v1/schedules//wake \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"note":"the inbox has new mail","in":"10m"}'
```
- `note` becomes the run's input. `in` is seconds or a string like `"90s"`, `"10m"`, `"2h"`; left out, the wake fires on the scheduler's next tick.
- A pending wake is re-timed, never stacked, so repeated wakes leave one job.
- A schedule with no saved workflow (a heartbeat) cannot be woken.
Behind a wake is the scheduler's `runAgain`: it creates one `@once` job for the bot.
## One-time jobs
An `@once` job fires once at its `run_at`, then deletes itself. A failed run may retry five minutes later first; the job is deleted on the final failure or on success. Because the job deletes itself, an [outbound hook](/bots/triggers-and-hooks#outbound-hooks) on it receives the run's last event and is then gone.
## Deliver the result
`parameters.deliver` is a list of `{channel, chat_id?, to?}`. A bare channel name such as `"slack"` means the owner's own account.
| Channel | Target |
|---|---|
| `inapp` | The Studio inbox. |
| `email` | The owner's registered address; other addresses must be confirmed recipients. |
| `slack`, `telegram`, `discord` | `chat_id`. |
| `whatsapp` | `to` (a phone number) and the sending `phone_number_id`. |
| `teams` | `chat_id`, a Microsoft Graph chat id. |
Delivery is fenced by the owner's pairing and a per-channel length cap. Any other channel is `422 invalid_params` and nothing is created.
- **`[SILENT]`:** when a run's result is exactly `[SILENT]`, or starts with it, the scheduler delivers to no channel and strips the marker from what it stores. A bot emits it when nothing is worth reporting. A run held for approval is never silent.
- **Threads:** a delivery target may carry the reply or thread id of its channel, so the answer lands in the originating thread.
- **Retries:** a failed delivery of a completed run is retried about 1 minute and then about 5 minutes later (three sends in all), then marked failed with an in-app notice.
- **Local bots:** a job for a bot that runs on the owner's machine is skipped cleanly, with one notice, when that machine is offline.
## Run history
`GET /v1/schedules/{id}/runs` lists the runs; `GET /v1/schedules/{id}` returns the schedule with its ten latest. A run has `status`, `started_at`, `ended_at`, `duration_ms`, `execution_id` and `error`. `POST /v1/schedules/{id}/run` runs it now and returns the `execution_id`, which `POST /v1/executions/{id}/cancel` accepts. `DELETE` removes the schedule and keeps its history readable.
# Bot to bot, groups and A2A
Source: https://www.plungeai.com/docs/bots/bot-to-bot-and-groups
A bot reaches another bot with the `message_bot` tool. It can reach one bot, a group of your bots, or an agent outside the platform over A2A. Nothing is sent to a recipient the bot is not allowed to reach.
## message_bot
List `message_bot` in `allowed_tools` and name the bots it may reach in `peers`:
```yaml
- type: harness
id: bot
agent: one-agent
goal: "{input}"
peers: [Research Bot, "a2a:https://agents.example.com/"]
allowed_tools: [message_bot, task_complete]
```
| Input | Meaning |
|---|---|
| `to` | A peer's name or id, `a2a:`, or `group:`. |
| `text` | The message. It must say everything the receiver needs: the receiver does not see the sender's conversation. |
| `wait` | `false` (default) queues the text and wakes the receiver. `true` runs the receiver now and returns its answer. |
- **The fence:** `to` must be in `peers`, compared without regard to case. No `peers` list means no messaging. An `a2a:` URL must be listed as written. A group is the exception: it is the owner's own grant, so membership of the group is the fence.
- **Queue and reply:** with `wait: false` the text lands in the receiver's inbox and the receiver wakes at once. Any reply arrives later as `[MESSAGE from ] ...` at the start of one of the sender's runs, so the sender finishes its own work first.
- **`wait: true` is a plain run:** the receiver runs as a child (depth plus one) with no memory, no inbox, no bot workspace and no bot tools. Use `wait: false` to reach the full bot.
- **Eight hops end a chain.** Each relay adds one; a message at the limit is refused, so two bots cannot loop forever.
- A bot cannot message itself, and a name that matches two bots is refused rather than guessed.
- Under `posture: read_only` the tool is denied (class `send`). `message_bot` results count as untrusted content.
- Cancelling the sender cancels the child runs it started with `wait: true`.
The inbox is readable by the owner: `GET /v1/workflows/{id}/inbox`. See [Workspace and inbox](/bots/workspace-and-inbox).
## Bot groups
Create, replace and delete answer 503 `not_enabled` until the platform owner enables it; the API documents the code.
A group is a named set of 2 to 6 of your own saved bots. It makes a team: one `message_bot` reaches all of them.
- `name` is 1 to 40 letters, digits, `_`, `.` or `-`, starting with a letter, digit or `_`, and unique per account.
- Members must all be your own bots, with no repeats.
- Create and edit groups in Studio under Bot groups, over REST (`/v1/bot-groups`), with the CLI (`bot-groups`) or over MCP.
```bash
curl -s -X POST https://api.plungeai.com/v1/bot-groups \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"name":"research-crew","members":["",""]}'
```
A bot in the group sends with `to: "group:research-crew"`:
- the sender must be a member (`not a member of research-crew` otherwise), and `wait: true` is refused;
- every other member's inbox gets the text as `[MESSAGE from in ]`, and the group's shared thread gets one entry;
- only the member after the sender in the group's order wakes, wrapping round, so a group never runs all its members at once.
Read the thread with `GET /v1/bot-groups/{id}/thread`, oldest first. It keeps the last 500 messages for 30 days. Deleting a group leaves its bots untouched.
## A2A: bots and agents outside
The `a2a` worker speaks the Agent2Agent protocol (1.0, with 0.3 compatibility) in both directions.
**An inbound door.** Any saved bot, agentic agent or workflow can have a door: `https://a2a.plungeai.com/`, with its own bearer token and agent card at `/.well-known/agent-card.json`. An outside A2A client's `SendMessage` runs the target as its owner and gets the final answer back as one text artifact. Reusing the `contextId` continues the conversation. A session that stops on a question answers `TASK_STATE_INPUT_REQUIRED`.
- Mint, rotate, pause, resume and revoke a door in Studio under **Share, A2A**, or over MCP with `plungeai_workflow` and `action: a2a_door` (`door_action`: `mint`, `get`, `pause`, `resume`, `revoke`).
- The token is shown once. Minting again rotates it.
**An outbound call.** A `peers` entry `a2a:` lets `message_bot` reach any A2A agent. The bearer comes from your Connections credential `a2a:`, or `a2a:/` for one door on a host. The reply text is scanned as untrusted content, and the hop count travels in the message metadata with the same limit of eight.
# Bot channels, voice and the chat widget
Source: https://www.plungeai.com/docs/bots/channels-and-voice
A bot can answer in the chats you already use. Every run started from a chat is a normal run of the bot, as its owner: it lands in your run history and stops at the bot's stored policy and spend caps.
## Pair a chat, bind it to a bot
Bind and unbind answer 503 `not_enabled` until the platform owner enables them, and the bindings list is empty until then; the API documents the code.
The channels are `telegram`, `whatsapp`, `discord` and `teams`; Slack is bound through the Slack app. Two steps:
1. **Pair the chat.** `POST /v1/channels/pair` with `{"channel": "telegram"}` returns a `code` valid for 10 minutes. Send it to the channel's bot from the chat (`/pair `). The chat then appears in `GET /v1/channels` with its `sender_id`.
2. **Bind it to a bot.** `POST /v1/workflows/{id}/bindings` with `{"channel": "telegram", "sender_id": ""}`.
```bash
curl -s -X POST https://api.plungeai.com/v1/channels/pair \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"channel":"telegram"}'
```
- A chat talks to **one** bot: binding it again moves it.
- A chat you did not pair is `422 not_paired`. An account at its binding limit is `409 binding_limit`.
- `GET /v1/workflows/{id}/bindings` lists the chats bound to a bot; `DELETE /v1/workflows/{id}/bindings/{channel}/{sender_id}` unbinds one.
- Studio has the same controls under the bot's channel bindings. The CLI has `workflows bindings list|bind|unbind`.
You can also bind from inside the chat: `/bot ` talks to one of your bots there, and `/unbind` releases it. On Slack the commands are `/plunge bind ` and `/plunge unbind`. If a bot and a flow share a name, the bot wins.
### Address one bot for one message
Start a message with `@` to run one bot on that message without changing the binding: `@research summarize this` runs the bot named `research` on `summarize this`, and the next plain message routes normally again.
- The name must match one of your bots exactly, ignoring case. The `@` must be the first character.
- Two bots with the same name are refused with a message, not guessed; no match answers `No bot named "" in your account.`
- Works on Telegram, WhatsApp, Discord and Slack.
## Voice notes
Voice notes work once the platform owner has deployed the updated message gateway, the same deploy as channel bindings.
A voice note sent to a paired Telegram or WhatsApp chat is transcribed with Whisper, and the transcript is handled like typed text.
- The audio is capped at 25 MB. A failed transcription answers with a fixed message asking you to type it or send it again.
- The reply to a voice note comes back as **audio**: the first 4,000 characters are spoken (`tts-1`, voice `nova`, mp3). When the reply is longer, or when speech or the send fails, the text is sent too or instead, so a voice note is never left unanswered.
- Typed messages are always answered in text.
- **Discord and Teams are text-only.**
- The paired owner's own OpenAI key is tried before the platform key.
## Microsoft Teams
Teams has two parts.
**Result delivery.** `parameters.deliver` takes `{"channel": "teams", "chat_id": ""}`, and the post is made as you, with your own Microsoft connection. With no Microsoft connection the delivery fails with `owner has no microsoft connection`; pairing is for inbound chats and is not what delivery needs. A channel target (`/`) is accepted by the format check but refused at delivery until the `ChannelMessage.Send` permission is granted.
**The two-way chat bot.** Through the Bot Framework, the gateway verifies each inbound activity and replies to the conversation it came from. It is text only.
The two-way Teams bot is off until the platform owner sets up its Azure bot registration, and `teams` cannot be paired before then. Result delivery does not need it.
## Channel security
The gateway refuses a channel request it cannot verify. A missing secret, a missing or wrong signature header and any error inside the check all answer `401`; only a POST is processed. Telegram is verified by its secret-token header, WhatsApp by an `X-Hub-Signature-256` HMAC and Discord by an Ed25519 signature.
## Web chat widget
A script tag puts a chat bubble for one bot on your own site.
```html
```
Creating or changing a widget answers 503 `widgets not enabled yet` until the platform owner enables it; the API documents the code. `embed.js` itself is always served.
Create the widget in Studio, from the bot's menu, with these settings:
| Setting | Meaning |
|---|---|
| Allowed origins | The sites that may frame the chat and call it. Only these origins are answered with CORS headers; never `*`. |
| Enabled | Off answers `403`. |
| Rate per minute | Messages per minute per widget, 1 to 600. Default 20. |
| Greeting | The first line the visitor sees, up to 300 characters. |
- The token is shown once, when the widget is created or rotated. Rotating stops the old snippet.
- Each visitor message is one run of the bot as its owner. The text is only ever the run's `input`, and the run's memory is the bot's own, not yours.
- The visitors spend your budget. Set a per-run spend cap on the bot first ([Permissions and budget](/bots/permissions-and-budget)); Studio warns when the bot has none. The per-run cap is the cost fence; the monthly cap only gates scheduled runs.
- Rate limits apply per address and per widget, a message is at most 4,000 characters, and a conversation id (valid 24 hours) keeps a visitor's thread together.
- The chat shell renders every reply as plain text, so a bot cannot inject markup into the page.
# Bot permissions and budget
Source: https://www.plungeai.com/docs/bots/permissions-and-budget
A bot's rules live in two places that are merged for every run: the `permissions` map in its YAML, and a policy stored on the bot's row. The engine loads the stored policy once per run, so a run by id, a schedule, a trigger, a chat message, MCP, the CLI and Studio all stop at the same holds and caps. A shared or templated bot cannot unlock itself, because locks come from the row, never from the YAML.
## Classes and presets
Every tool call has a class: `send`, `write`, `pay`, `delete` or `read`. A preset says what happens to a class (or to one tool name):
| Preset | Effect |
|---|---|
| `allow` | The call runs. |
| `ask` | The run pauses and waits for the owner. |
| `deny` | The call does not run. |
| `hand_off` | A deny that does not retry. The call never runs, and the result tells the owner it is theirs to do. Only One Agent understands it; any other runtime gets `deny`. |
The stored policy covers `send`, `write`, `pay` and `delete`. In YAML, `permissions` can also name `read` and individual tools:
```yaml
- type: harness
id: bot
agent: one-agent
permissions:
pay: hand_off
send: ask
budget_usd_run: 0.5
```
How the order of checks works is on [Policy and review](/one-agent/policy-and-review). Two rules to remember: an operation named `buy`, `purchase`, `fetch_paid`, `send`, `transfer`, `pay`, `send_payment`, `withdraw` or `shop` is on the money floor and always `ask`, even with `send: allow`, and `posture: read_only` denies every mutating class whatever the presets say.
## Set the stored policy
`PUT /v1/workflows/{id}/permissions` replaces the fields you send and leaves the others. It is owner-only.
```bash
curl -s -X PUT https://api.plungeai.com/v1/workflows//permissions \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"classes":{"send":"ask","pay":"deny"},"budget_usd_run":0.25,"budget_usd_month":10}'
```
| Field | Meaning |
|---|---|
| `classes` | A preset per class: `send`, `write`, `pay`, `delete`. A class you leave out falls back to the bot's YAML, then to the default. |
| `locks` | Classes an admin locked. A locked class lifts the bot's own `allow` to `ask`; it never lifts a `deny`. |
| `budget_usd_run` | Cap on one run, in USD. A positive number sets it and `null` removes it; `0` is stored but means no cap. |
| `budget_usd_month` | Cap on a month, in USD. A positive number sets it and `null` removes it; `0` is stored but means no cap. |
`GET /v1/workflows/{id}/permissions` returns the stored policy. The same settings are in Studio under the bot's Permissions and Budget fields, on MCP, and on the CLI:
```bash
ocean-cli workflows permissions set --class pay=ask --lock pay --budget-run 0.5 --budget-month 20
```
- **Locks need a team admin** over the workflow: the project owner or an admin collaborator, or a company owner or admin. Anyone else gets `403 lock_admin_required`, and nothing in that body is written.
- An invalid field is `422 invalid_params` and nothing is written.
## How the YAML and the row combine
| Part | Rule |
|---|---|
| Class presets | The two are merged; the YAML's own entry wins per class. |
| Locks | The row's locks replace whatever the YAML carried. |
| `budget_usd_run` | The lower of the YAML's value and the row's wins. |
## Approvals
A run that hits an `ask` pauses with status `needs_approval` and a `continuation` that carries the pending action, its price and its class. Answer it with `POST /v1/executions/{id}/continue`:
```bash
curl -s -X POST https://api.plungeai.com/v1/executions//continue \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"approve":true,"allow_scope":"run"}'
```
| `allow_scope` | The approval reaches |
|---|---|
| `once` (default) | This call only. |
| `run` | The same tool for the rest of the run. |
| `always` | Not written by this call: it behaves as `once` for the run, and the next gated call asks again. The rule is the class preset stored with `PUT .../permissions`. Studio's "Allow always for this bot" stores the preset first and then approves; over REST, do the `PUT` yourself. |
`{"answer": "..."}` replies to a question, or denies or redirects an approval. An unattended run that hits an `ask` waits for the owner, up to seven days.
## Spend caps
- **Per run.** When the running token cost passes `budget_usd_run`, the run stops at the next turn with `stop_reason: budget`, and the result says `[run stopped at $... per-run budget cap after N turns ...]`. The engine applies it on every door.
- **Per month.** `budget_usd_month` is enforced by the scheduler and gates scheduled runs only. A chat message or a web widget visitor is bounded by the per-run cap, which is why a bot behind a public surface needs one.
# Bot workspace, inbox and memory
Source: https://www.plungeai.com/docs/bots/workspace-and-inbox
A run is short; a bot is not. Three things carry over from one run to the next: the files it writes, the messages other bots left for it, and what it remembers.
## Workspace files
The file tools (`read_file`, `write_file`, `edit_file`, `list_files`, `search_files`, `delete_file`) work on a virtual workspace. The `workspace` key says how long it lives:
| `workspace` | Files |
|---|---|
| `bot` | Kept across runs. The default for a saved bot. |
| `run` | Start clean each run. |
A bot that takes screenshots with `browser` finds them under `/shots/` in the same workspace.
### Read them
`GET /v1/workflows/{id}/workspace-files` lists the persistent files, newest first, up to the 1,000 newest (`truncated` is `true` when there are more). `GET /v1/workflows/{id}/workspace-files/download?path=` returns one file as raw bytes with its stored content type and `Content-Disposition: attachment`. Both are owner-only.
```bash
curl -s https://api.plungeai.com/v1/workflows//workspace-files \
-H "Authorization: Bearer $PLUNGE_API_KEY"
curl -s -o report.md "https://api.plungeai.com/v1/workflows//workspace-files/download?path=notes/report.md" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
| Status and code | When |
|---|---|
| `400 invalid_path` | The path has a `..` segment, a backslash, a control character, an absolute path outside `/ws`, or an encoded form of any of these. |
| `404 file_not_found` | The path is not a file the listing shows. |
| `413 file_too_large` | The file is over 20 MB. |
The CLI is `ocean-cli workflows files [--download --out ]`. A run's own deliverables (spreadsheets, documents, decks) are listed per execution by `GET /v1/executions/{id}/files`.
## Inbox
When another bot sends a message with `message_bot`, the text is queued in the receiver's inbox and the receiver wakes. At the start of its next run the bot reads the queue as lines of the form `[MESSAGE from ] `, or `[MESSAGE from in ] ` for a group message, and the queue is cleared.
- A message waits up to 7 days.
- Eight relays end a chain, so two bots cannot loop forever.
`GET /v1/workflows/{id}/inbox` shows what is queued, newest first. It is read-only and owner-only. See [Bot to bot and groups](/bots/bot-to-bot-and-groups).
## Memory
A bot keeps durable facts with the `memory` tool and searches its past work with `recall` and `recall_history`.
- **`memory`** applies a batch of `operations` (`add`, `replace`, `remove`) to the `user` or `memory` target in one call. `read` and `forget {query}` are single calls of their own; `forget` purges every entry containing the text.
- **Whose memory.** A bot uses the memory of the user who runs it; `memory_owner` is refused at save. A bot's own namespace (`:bot:`) is wiped when the bot is deleted.
- **Account memory over REST.** `GET /v1/memory/recall`, `POST /v1/memory/remember` and `POST /v1/memory/forget` read and write the facts every bot and agent you run can recall. `GET /v1/memory/runs?q=` searches your past runs (full text once the platform owner has enabled the index, a plain substring match until then), and `GET /v1/memory/runs/{id}` returns one stored result.
- **Weekly consolidation (opt-in, default off).** With `memory_consolidate: weekly` in the bot's `bot-config` block, Studio creates a second job, ` · memory consolidation`, that runs on Sundays at 03:00. It replays the owner's recent runs through the memory review and records `reviewed N episodes`. With no key set there is no job, so nothing runs.
## Steering a running bot
`POST /v1/executions/{id}/steer` queues a message the loop reads at its next turn. A finished run answers `409 execution_finished`. `POST /v1/executions/{id}/followup` messages a run in flight the same way, and starts a new turn once the run has finished.
# Bot triggers and hooks
Source: https://www.plungeai.com/docs/bots/triggers-and-hooks
A trigger starts a bot from an event outside the platform. Every trigger hangs off a schedule of the bot (see [Schedules and wake](/bots/schedules-and-wake)), so the run goes through the same stored policy, budget and delivery as any other run. Hooks go the other way: they send the bot's run events to a URL you own.
## Inbound
| Trigger | You get | Where to set it |
|---|---|---|
| Webhook | An https URL and a secret | REST, MCP, CLI |
| Email | An address | REST, MCP, CLI |
| Slack channel or keyword | A bot started by a message | Studio Triggers, Slack card |
| Gmail new mail | A bot started once per new mail | Studio Triggers, Gmail card |
| GitHub or GitLab event | A webhook preset with an event allow-list | Studio Triggers, Git card (the Studio route to a webhook) |
The REST routes live under `/v1/triggers/{kind}`. Every call names the schedule with `schedule_id`: in the body for `POST`, in the query for `GET` and `DELETE`. Mint answers `201` with `Cache-Control: no-store`. **A secret is in the mint response once and is never listed again.** Revoking an id that is not on the schedule is `404 trigger_not_found`. Webhook and email triggers also rotate: `POST /v1/triggers/{webhook|email}/{token}/rotate` mints a successor with the same settings and a new secret, then revokes the old token.
### Webhook
```bash
curl -s -X POST https://api.plungeai.com/v1/triggers/webhook \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"schedule_id":"","scheme":"github","prompt_template":"New PR: {pull_request.title}","filters":{"all":[{"path":"action","op":"equals","value":"opened"}]}}'
```
The response carries `token`, `secret`, `scheme` and the `url` to give the sender.
| Field | Meaning |
|---|---|
| `scheme` | How the sender signs: `generic` (default, `X-Webhook-Signature` is the hex HMAC-SHA256 of the raw body), `github`, `gitlab` or `standard-webhooks`. |
| `prompt_template` | The run's input. `{dotted.path}` fills in a field of the JSON payload. |
| `filters` | `{all?, any?}` of rules `{path, op, value?}`, up to 20. `op` is `equals`, `contains`, `exists` or `in`. A path that starts with `headers.` reads a request header. |
What the sender gets back, in order:
| Answer | When |
|---|---|
| `401` | Bad signature, or a timestamp more than 300 seconds off. |
| `409` | A delivery id seen in the last hour. |
| `204` | The filters did not match. The delivery is acknowledged, no run starts and nothing is spent. |
### Email
`POST /v1/triggers/email` with `{"schedule_id": "..."}` returns a `token` and an `address_local_part` of the form `bot+`; the address is that local part at the Email Routing domain your operator set up. Mail to it starts the schedule's bot. `allowed_from` (comma-separated addresses or domains) restricts the senders; it is a sender filter, weaker than a webhook signature.
### Slack channel or keyword
Answers 503 `slack triggers not enabled yet` until the platform owner enables it; the API documents the code.
In Studio, open the bot's Triggers and add a Slack trigger: a channel, and optionally a keyword. A human message in the channel that contains the keyword (the longest keyword wins; no keyword means every message) starts the bot with the input `from <@author> in <#channel>: text`. The run is signed as the trigger owner, so the bot's policy and the owner's budget apply.
- App mentions, bot posts, edits and other subtypes never match, so there is no loop. Thread replies in the channel do fire.
- Creating the trigger requires your own membership of the channel, checked server-side and fail-closed.
- The channel can be added to the job's delivery, so the result lands there.
- Anyone who can post in the channel spends the owner's budget: pick channels you trust.
### Gmail new mail
In Studio, add a Gmail trigger: a Gmail search such as `from:billing@ subject:invoice` and a cadence of 5, 15 or 60 minutes. Studio saves a small check workflow and a heartbeat schedule. Each tick the scheduler runs the check as the owner, using the owner's own Google connection, and a YES starts the bot once per mail it has not started before, with the mail (From, Subject, Date, id and the plain-text body, up to 8,000 characters) as the run's input.
- A mail starts at most one run. Only mail that arrived after the trigger was created counts, with at most 5 runs per tick.
- A failed run is not retried, because a retry could repeat side effects. It shows in the bot's run history.
- This is a poll: the cadence is the latency, and the YES or NO check costs one small model call per tick. A tick that finds no new mail puts nothing on the board.
- Mail content is untrusted input. Choose searches you trust and keep the bot's tools narrow.
### GitHub and GitLab
The Git card mints a `github` or `gitlab` webhook with an allow-list of events, then shows the URL and the secret once and where to paste them: in GitHub, repository Settings, Webhooks, Payload URL and Secret with `application/json`; in GitLab, Settings, Webhooks, URL and Secret token. Events outside the allow-list, including GitHub's `ping`, are acknowledged and ignored.
### Deliver-only
A job with `parameters.deliver_only: true` never calls the engine or a model. When its trigger fires, the scheduler fills `parameters.template` (`{input}` is the incoming text) and posts it to the job's `deliver[]` targets, so a webhook can be forwarded to a channel at no model cost. The run history shows the filled text and no cost.
- Slack and Discord pings and links in the incoming text (`@everyone`, ``, `<@U...>`, ``) are broken with an invisible character. Your own template is posted as written.
- Other markup passes through, so put a `filters` allow-list on the trigger, and `allowed_from` on an email trigger.
- A deliver-only job needs a non-empty `deliver[]` and a `template`.
## Outbound hooks
Answers 503 `unavailable` until the platform owner enables it; the API documents the code.
A hook sends the schedule's run events to an https URL you own.
```bash
curl -s -X POST https://api.plungeai.com/v1/triggers/hooks \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"schedule_id":"","url":"https://example.com/ocean-hook","events":["run.finished","run.failed"]}'
```
The response carries the hook `id` and a `secret`, shown once. The URL must be https, public, with no credentials in it, and it is re-checked before every delivery. A schedule has at most 10 hooks.
| Event | When |
|---|---|
| `run.started` | The run began. |
| `run.finished` | The run completed. |
| `run.failed` | The run failed, including an agent refusal that the history records as failed. |
| `run.needs_approval` | The run stopped on a held action. |
The body is `{event, delivery_id, timestamp, job_id, job_name, workflow_id, execution_id, run_id, status, result_summary?, error?}`. `execution_id` joins one run's events.
Each delivery carries three headers: `X-Ocean-Delivery-Id`, `X-Ocean-Timestamp` (unix seconds) and `X-Ocean-Signature`, which is `sha256=` plus the hex HMAC-SHA256 of `..` under the secret. Recompute it over the raw body, compare in constant time, and reject an old timestamp:
```javascript
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verify(secret, headers, rawBody) {
const id = headers['x-ocean-delivery-id']
const ts = headers['x-ocean-timestamp']
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false
const want = 'sha256=' + createHmac('sha256', secret).update(`${id}.${ts}.${rawBody}`).digest('hex')
const got = String(headers['x-ocean-signature'] ?? '')
return got.length === want.length && timingSafeEqual(Buffer.from(got), Buffer.from(want))
}
```
- **Best effort.** One retry after a second, only on a network error, a timeout (10 seconds), a 5xx, a 408 or a 429. Any other answer is final. The retry sends the identical body, id and signature, so dedupe on `X-Ocean-Delivery-Id`. Redirects are never followed. A hook failure never fails or delays the run.
- **Scope.** Scheduler-path runs only: cron, heartbeat, run now and triggers. An ad-hoc run from Studio or the API emits nothing.
- **No ordering.** Events are sent as they happen, each independently. Order by `timestamp` and join on `execution_id`.
- **One-time jobs.** An `@once` job deletes itself when its run ends, and its hooks with it. The run's last event is still delivered.
# Control a bot over REST, MCP and the CLI
Source: https://www.plungeai.com/docs/bots/rest-mcp-cli
A bot is the same row whichever door you use. The One API, the MCP server and the Ocean CLI read and write the rows Studio shows, take the same `ozk_` key, and stop at the same stored policy. Pick the door that fits the caller: REST for code, MCP for an agent, the CLI for a terminal.
| Door | Address | Auth |
|---|---|---|
| One API 2.3.0 | `https://api.plungeai.com` | `Authorization: Bearer $PLUNGE_API_KEY` |
| MCP server 2.6.0 | `https://mcp.plungeai.com/v1` | The same key; tools are named `plungeai_*` |
| Ocean CLI 2.1.0 | `ocean-cli` | `ocean-cli login`, or `OCEAN_API_KEY` |
Another account's id answers the same `404 _not_found` as an id that does not exist. A storage or upstream failure is a generic `502`; the message never carries database text. Every error is `{"error": {"code", "message"}}` with an `X-Error-Code` header. The response format is yours to pick ([four wires](/api-reference/four-wires)).
## Operations by door
| Do this | REST | MCP | CLI |
|---|---|---|---|
| Save a bot | `POST /v1/workflows` with `kind: bot` | `plungeai_workflow` `create` | `workflows create NAME FILE.yaml --kind bot` |
| Check the YAML | The save runs the lint and answers `422 invalid_workflow` | The same, on `create` and `update` | `workflows validate FILE.yaml --kind bot` (local) |
| Change it, version it | `PATCH /v1/workflows/{id}`, `POST .../versions` | `update`, `save_version`, `restore_version` | `workflows update`, `save-version`, `restore` |
| Copy a template | `POST /v1/templates/{id}/use` | `plungeai_templates` | `templates` |
| Read or set permissions and caps | `GET`, `PUT /v1/workflows/{id}/permissions` | `plungeai_workflow` `permissions_get`, `permissions_set` | `workflows permissions get`, `set` |
| Run it | `POST /v1/workflows/{id}/execute` | `plungeai_execute_workflow` | `workflows run ID` |
| Answer a pause or approve | `POST /v1/executions/{id}/continue` | `plungeai_continue` | `executions continue ID` |
| Steer a running bot | `POST /v1/executions/{id}/steer` | `plungeai_executions` `steer` | `executions steer ID TEXT` |
| List a run's files | `GET /v1/executions/{id}/files` | `plungeai_executions` `files` | `executions files ID` |
| Schedule it | `POST /v1/schedules` (`run_at`, `ends_at`) | `plungeai_schedule` `create` | `schedules create` (`--cron @once --at`, `--ends-at`) |
| Wake it | `POST /v1/schedules/{id}/wake` | `plungeai_schedule` `wake` | `schedules wake ID --note TEXT` |
| Add a trigger or hook | `POST /v1/triggers/{webhook,email,hooks}` | `plungeai_schedule` `webhook_mint`, `email_mint`, `hooks_mint` (and `_list`, `_delete`) | `schedules triggers webhook\|email\|hooks mint\|list\|delete` |
| Read its workspace | `GET /v1/workflows/{id}/workspace-files`, `.../download?path=` | `plungeai_workflow` `files`, `file` | `workflows files ID [--download PATH --out FILE]` |
| Read its inbox | `GET /v1/workflows/{id}/inbox` | `plungeai_workflow` `inbox` | `workflows inbox ID` |
| Bind a chat | `POST /v1/workflows/{id}/bindings`, `DELETE .../{channel}/{sender_id}` | `plungeai_workflow` `bindings`, `bind`, `unbind` | `workflows bindings list\|bind\|unbind` |
| Pair a chat | `POST /v1/channels/pair`, `GET /v1/channels`; then send the code from the chat | Studio or REST | Studio or REST |
| Manage bot groups | `/v1/bot-groups`, `.../{id}/thread` | `plungeai_workflow` `groups`, `group_set`, `group_delete`, `group_thread` | `bot-groups list\|create\|update\|delete\|thread` |
| Give it an A2A door | No REST route; Studio (Share, A2A) | `plungeai_workflow` `a2a_door` | Studio (Share, A2A) |
| Search its runs, its memory | `GET /v1/memory/runs`, `/v1/memory/recall` | `plungeai_memory` | `memory` |
The operation pages are in the API reference: [Bots](/api-reference/bots/get-bot-permissions), [Schedules](/api-reference/schedules/create-schedule), [Triggers](/api-reference/triggers/create-webhook-trigger) and [Executions](/api-reference/executions/steer-execution).
## The agent call door
`POST /v1/execute` takes the program as the body and always runs through the engine, so a saved bot keeps its stored policy. The body is JSON or YAML:
| Body | Runs |
|---|---|
| `agent: ` and `input` | Your saved agent or bot, from its live YAML. |
| `agent: @3` and `input` | Snapshot 3 of it: a pinned version. |
| `agent: ` and that agent's own fields | A registry agent as a one-task workflow. |
| `workflow:`, or a root `name` and `tasks:` | An inline workflow. |
`dry_run: true` validates and reports what would run without running it, spending anything or writing an execution row. `stream: true` answers as server-sent events. See [Agent call](/api-reference/agent-call/agent-call).
## MCP
- Every tool takes `user_request` (the user's words) and most take `format`: `markdown` (default) or `json`. A tool that runs something also takes `user_context` (time zone, locale, place), merged over the zone the network request implies.
- `plungeai_get_result` with `format: json` returns the structured result (`status`, `result`, `tasks`), not the rendered thread.
- `plungeai_workflow` `create` takes `kind`: `workflow`, `agent`, `bot` or `campaign`.
- `plungeai_schedule` `create` with `schedule: "@once"` needs `run_at`: without it the answer is `run_at required`.
- `permissions_set` takes `classes`, `locks`, `budget_usd_run` and `budget_usd_month`; `locks` needs a team admin.
- `file` returns a workspace file as base64, up to 5 MiB; a bigger file is refused with the REST download route to use instead.
## CLI
The verbs below are in Ocean CLI 2.1.0. Run it from the repository today with `npm run ocean-cli -- `; once it is published it installs with `npm install -g @plungeai/ocean` and runs as `ocean-cli`.
- Every verb takes `--json` (one JSON document on stdout) and, when it creates, runs or pays, `--dry-run` (prints the exact request and sends nothing).
- Destructive verbs (delete, unbind, restore, forget) need `--yes`.
- `workflows permissions set` reads the stored classes and locks first and sends only what you name, so one `--class` keeps the others.
- Exit codes: 2 bad input, 3 auth, 4 API error or a run that needs input or approval, 5 timeout.
```bash
ocean-cli workflows permissions set --class pay=ask --lock pay --budget-run 0.5 --budget-month 20
ocean-cli schedules create "Reminder" --type workflow --target --cron @once --at 2026-10-06T09:00:00Z
ocean-cli bot-groups create --name research-crew --members ,
ocean-cli executions steer "also check the pricing page"
```
# Companies
Source: https://www.plungeai.com/docs/bots/companies
A company is a **campus**. You write it as YAML documents of two kinds and save them like any workflow. A `kind: campus` document names the buildings, and a `kind: building` document (or a building written inline in the campus) names its floors of offices, the standard its policy follows, how items come in, and the pipeline an item walks. An office is an ordinary workflow: the agents doing the work.
| Part | What it is |
|---|---|
| Campus | the company: a slug, a mode and its buildings in map order |
| Building | offices on floors, a policy (`code` or `action` standard), intake and a pipeline. `location: cloud` runs on the platform, and `location: local` runs on your own machine |
| Office | a saved workflow, or one written inline that apply saves as `//` |
| Item | one piece of work put into a building; it walks the pipeline step by step |
| Gate | a pipeline step where the mode and the policy decide: pass, or ask a person (an approval) |
The mode decides who passes a gate. `supervised` asks a person at every gate. `tiered` asks according to the standard's tiers. `autonomous` asks only where the policy says `require`.
## The lifecycle
1. Save the documents with [`POST /v1/workflows`](/api-reference/workflows/create-workflow) and `kind: campus` or `kind: building`.
2. [Apply](/api-reference/companies/apply-company): compile them into the stored plan.
3. [Start the clock](/api-reference/companies/start-company): cloud buildings begin walking their items.
4. [Put items in](/api-reference/companies/create-company-item) and watch the [snapshot](/api-reference/companies/get-company-snapshot) or the [event feed](/api-reference/companies/list-company-events).
5. Decide the [approvals](/api-reference/companies/list-company-approvals) waiting at the door: [approve or reject](/api-reference/companies/decide-company-approval).
6. [Stop the clock](/api-reference/companies/stop-company), then [delete](/api-reference/companies/delete-company) the company. Delete removes every company row. Your saved documents stay until you delete them.
Every route is in the API reference under **Companies**. The One API guide's Companies chapter (`api.plungeai.com/docs`, chapter 17) walks the whole lifecycle with live responses. The errors are `404 company_not_found` for a campus that is not yours, `409 conflict` for deleting a running company, and `422 invalid_params` for a body that breaks a rule.
# Developer Tools Overview
Source: https://www.plungeai.com/docs/developer-tools/quickstart
Planned: not available yet. Tracked as TI-74.
## What this will do
MCP vs One API vs CLI vs Studio: pick your door. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Account API
Source: https://www.plungeai.com/docs/developer-tools/account-api
Planned: not available yet. Tracked as TI-06.
## What this will do
A key-authenticated Account API will let a script manage keys, the wallet and usage without the Dashboard. How that API authenticates is still open. Today every account action is a Dashboard view; the table below lists where each one lives.
## Available today in the Dashboard
| What it does | Dashboard location |
|---|---|
| Create, list and revoke `ozk_` keys (legacy `sk-ocean-` and `sk-conn-` keys: list and revoke only) | **Dashboard → One API → Keys** |
| Wallet balance, top-up, auto-recharge, BYOK provider keys | **Dashboard → One API → Billing** |
| Ledger of top-ups and charges | **Dashboard → Billing → Ledger** |
| Usage by user, workflow, model or channel | **Dashboard → Billing → Usage** |
| Request log of One API calls | **Dashboard → One API → Requests** |
| Model and connector catalog (provider cost basis) | **Dashboard → One API → Catalog** |
| Connected accounts | **Dashboard → Secrets & Connections → Connections** |
| Teams, companies and roles | **Dashboard → Team** and **Dashboard → Workspace** |
| Who am I (identity, tier, rate window) | Key-authenticated today over MCP: [plungeai_whoami](/account-api/identity/whoami) |
## Use this today
Create and revoke keys in the Dashboard.
Where the wallet balance lives today.
# Install PlungeAI Agent Skills
Source: https://www.plungeai.com/docs/developer-tools/agent-skills
Planned: not available yet. Tracked as TI-74.
## What this will do
31 skills that teach coding agents to use PlungeAI. This page is not written yet; the source below covers the same material today.
## Use this today
All 31 agent skills with their install options.
# Anthropic Tool Calling
Source: https://www.plungeai.com/docs/developer-tools/anthropic-tool-calling
Planned: not available yet. Tracked as TI-74.
## What this will do
Turn PlungeAI tool contracts into Claude tool definitions. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Claude Code Plugin
Source: https://www.plungeai.com/docs/developer-tools/claude-code-plugin
Planned: not available yet. Tracked as TI-74.
## What this will do
The `plungeai` plugin: 31 skills plus its own MCP server config. Install it instead of `claude mcp add`, not in addition. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Ocean CLI
Source: https://www.plungeai.com/docs/developer-tools/cli
Planned: not available yet. Tracked as TI-74.
## What this will do
The `ocean` command: auth, runs, workflows, schedules, registry. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# Cursor Plugin
Source: https://www.plungeai.com/docs/developer-tools/cursor-plugin
Planned: not available yet. Tracked as TI-13.
## What this will do
A Cursor plugin will install the PlungeAI MCP server and skills from the Cursor marketplace in one step. No Cursor plugin exists today; Cursor connects through its MCP config.
## Use this today
Connect Cursor to `https://mcp.plungeai.com/v1` with the one-click install.
# LangChain
Source: https://www.plungeai.com/docs/developer-tools/langchain
Planned: not available yet. Tracked as TI-74.
## What this will do
Use the models plane from LangChain's `ChatOpenAI`. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# PlungeAI MCP Server Quickstart
Source: https://www.plungeai.com/docs/developer-tools/mcp/quickstart
The PlungeAI MCP server gives any MCP client the 20 `plungeai_*` tools: discover capabilities, run tools, agents, workflows and missions, schedule jobs and read results.
## When to use PlungeAI MCP?
All four surfaces use the same `ozk_` key, the same account and the same data.
| Surface | What it is | Use it when |
|---|---|---|
| MCP server, `https://mcp.plungeai.com/v1` | The 20 `plungeai_*` tools over MCP | An AI client operates PlungeAI live: discover, run, save, schedule, remember, with approvals and continuations |
| One API, `https://api.plungeai.com` | REST routes, OpenAPI at `GET /v1/openapi.json` | You write or generate code that calls PlungeAI |
| [Skills](/skills), zips at `https://skills.plungeai.com` | 31 instruction packs your AI client loads | Your client should already know PlungeAI's conventions; skills complement a connection |
| Ocean Studio, `https://studio.plungeai.com` | The browser app | A person builds visually, connects accounts and reviews runs |
A docs MCP server that searches and reads these pages from your agent is planned, tracked as TI-35: see [Docs MCP](/developer-tools/mcp/docs-mcp).
## Endpoint
| Item | Value |
|---|---|
| URL | `https://mcp.plungeai.com/v1` |
| Transport | Streamable HTTP, JSON-RPC 2.0 over `POST` |
| Authentication | `Authorization: Bearer $PLUNGE_API_KEY` or `X-API-Key: $PLUNGE_API_KEY` (an `ozk_` key); every request needs it, `tools/list` included |
| Responses | JSON; `tools/call` streams as server-sent events |
Create the key at **Dashboard → One API → Keys** (https://dashboard.plungeai.com/one-api?tab=keys) and export it as `PLUNGE_API_KEY` before you configure a client.
## Quick installation
### Let your agent install it
Paste this prompt into your coding agent; it reads the setup file and connects itself:
```text Onboard from /agents.md
Use curl to read https://www.plungeai.com/docs/agents.md and perform the setup to connect PlungeAI
```
Or name the server directly:
```text Add the PlungeAI MCP server
Add the MCP server https://mcp.plungeai.com/v1 named plungeai to my user-level client config, with the header Authorization: Bearer and the value of my PLUNGE_API_KEY environment variable, then call plungeai_whoami to confirm the connection.
```
### Client installation
Claude Code, one command:
```bash
claude mcp add --transport http plungeai https://mcp.plungeai.com/v1 \
--header "Authorization: Bearer $PLUNGE_API_KEY" --scope user
```
Codex, which reads the key from `PLUNGEAI_API_KEY`:
```bash
export PLUNGEAI_API_KEY="$PLUNGE_API_KEY"
codex mcp add plungeai --url https://mcp.plungeai.com/v1 --bearer-token-env-var PLUNGEAI_API_KEY
```
A project `.mcp.json` that reads the key from each developer's environment, safe to commit:
```json
{
"mcpServers": {
"plungeai": {
"type": "http",
"url": "https://mcp.plungeai.com/v1",
"headers": { "Authorization": "Bearer ${PLUNGE_API_KEY}" }
}
}
}
```
One-click installs open your client's install dialog with the URL filled in; add the key afterwards in the client's user-level config:
Opens Cursor's MCP install dialog.
Opens VS Code's MCP install dialog.
Adds the server to Replit Agent.
Step-by-step pages per client:
User-level `~/.cursor/mcp.json`.
Copilot agent mode.
Integrations → MCP Servers.
Verify in any client: ask it to use plungeai_whoami to confirm your identity.
These docs use PLUNGE_API_KEY; the PlungeAI skills, the Claude Code plugin and the install page use PLUNGEAI_API_KEY; any name works if your config and shell agree.
# Programmatic use
Source: https://www.plungeai.com/docs/developer-tools/mcp/programmatic-use
Planned: not available yet. Tracked as TI-74.
## What this will do
Call the MCP server from your own code with raw JSON-RPC, and run outbound MCP servers with `/v1/mcp/runs`. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# PlungeAI MCP
Source: https://www.plungeai.com/docs/developer-tools/mcp/plungeai-mcp
Planned: not available yet. Tracked as TI-74.
## What this will do
The one server: install per client, configure, troubleshoot. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Workflows and missions over MCP
Source: https://www.plungeai.com/docs/developer-tools/mcp/workflows-mcp
Planned: not available yet. Tracked as TI-74.
## What this will do
Run workflows and missions from your coding agent, sync or async. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Protocol and transport
Source: https://www.plungeai.com/docs/developer-tools/mcp/protocol
Planned: not available yet. Tracked as TI-74.
## What this will do
Streamable HTTP, JSON-RPC methods, sessions, SSE progress. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Prompts and resources
Source: https://www.plungeai.com/docs/developer-tools/mcp/prompts-resources
Planned: not available yet. Tracked as TI-74.
## What this will do
The `/plungeai` prompt and every `plungeai://` resource. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Move MCP clients from /mcp to /v1
Source: https://www.plungeai.com/docs/developer-tools/mcp/migrate-to-v1
Planned: not available yet. Tracked as TI-74.
## What this will do
`/mcp` is a legacy alias since 2026-09-20; configure `/v1`. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Docs MCP server
Source: https://www.plungeai.com/docs/developer-tools/mcp/docs-mcp
Planned: not available yet. Tracked as TI-35.
## What this will do
The docs at www.plungeai.com/docs will run their own MCP server so an agent can search these pages and read any of them as markdown. It is a separate server from the PlungeAI MCP server, and it is not running yet.
## Use this today
Read these docs as markdown today: llms.txt, llms-full.txt and a .md twin of every page.
# OAuth Provider
Source: https://www.plungeai.com/docs/developer-tools/oauth-provider
Planned: not available yet. Tracked as TI-05.
## What this will do
MCP clients that connect only through OAuth will be able to sign in with a PlungeAI account instead of sending a key. Today the MCP server accepts bearer `ozk_` keys only.
## Use this today
Connect any MCP client with an ozk_ key in the Authorization header.
# Ollama Tool Calling
Source: https://www.plungeai.com/docs/developer-tools/ollama-tool-calling
Planned: not available yet. Tracked as TI-74.
## What this will do
Turn PlungeAI tool contracts into tool definitions for a local Ollama model through its OpenAI-compatible endpoint. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# OpenAI SDK
Source: https://www.plungeai.com/docs/developer-tools/openai-sdk
Planned: not available yet. Tracked as TI-74.
## What this will do
Point the OpenAI Python or TypeScript SDK at PlungeAI. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# OpenAI Tool Calling
Source: https://www.plungeai.com/docs/developer-tools/openai-tool-calling
Planned: not available yet. Tracked as TI-74.
## What this will do
Turn tool contracts into OpenAI function definitions and execute the calls. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Generate a client from OpenAPI
Source: https://www.plungeai.com/docs/developer-tools/openapi-codegen
Planned: not available yet. Tracked as TI-74.
## What this will do
Typed clients from `/v1/openapi.json`. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill reference that covers the same ground.
# OpenCode Plugin
Source: https://www.plungeai.com/docs/developer-tools/opencode-plugin
Planned: not available yet. Tracked as TI-74.
## What this will do
Connect OpenCode today with an MCP config; a plugin is planned. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# @plungeai/one-api
Source: https://www.plungeai.com/docs/sdks/typescript
The official TypeScript SDK for the PlungeAI One API (`https://api.plungeai.com`). Targets One API `2.5.0`; package version `1.0.0`. The resource methods and their types are generated from the API's own OpenAPI document (`GET /v1/openapi.json`). Zero runtime dependencies.
Release status: `npm view @plungeai/one-api version`; until it answers, build from `sdk/one-api-client/` (`npm ci && npm run build`).
## Installation
```sh
npm install @plungeai/one-api
```
## Usage
```ts
import PlungeAI from '@plungeai/one-api'
const client = new PlungeAI({
apiKey: process.env['PLUNGEAI_API_KEY'], // the default; can be omitted
})
const me = await client.whoami()
const run = await client.agents.execute('calculator-agent', { prompt: '6*7' })
const hits = await client.discovery.search({ q: 'web search', kind: 'agents' })
```
## Streaming responses
```ts
const stream = await client.workflows.executeInlineStream({ workflow })
for await (const event of stream) {
if (event.event === 'token') process.stdout.write((event.data as { delta: string }).delta)
}
```
`break` out of the loop or call `stream.controller.abort()` to stop; either aborts the underlying request. `stream.tee()` splits a stream in two, `stream.toReadableStream()` hands it on as a web `ReadableStream`. Methods that take `stream: true` (`client.agents.execute`, `client.chat.completions.create`) return the same `Stream`.
## Auto-pagination
```ts
for await (const w of client.workflows.list({ kind: 'bot' })) {
console.log(w)
}
let page = await client.workflows.list({ limit: 50 })
for (const w of page.data) console.log(w)
while (page.hasNextPage()) {
page = await page.getNextPage()
for (const w of page.data) console.log(w)
}
```
## Handling errors
```ts
try {
await client.agents.execute('calculator-agent', { prompt: '6*7' })
} catch (err) {
if (err instanceof PlungeAI.FailedDependencyError) console.log(err.connect?.link)
else if (err instanceof PlungeAI.APIError) console.log(err.status, err.code, err.requestID)
else throw err
}
```
| Status | Error class |
|---|---|
| 400 | `BadRequestError` |
| 401 | `AuthenticationError` |
| 403 | `PermissionDeniedError` |
| 404 | `NotFoundError` |
| 409 | `ConflictError` |
| 422 | `UnprocessableEntityError` |
| 424 | `FailedDependencyError` — `connect: { provider, method, link }` says where to connect the missing account |
| 429 | `RateLimitError` |
| ≥ 500 | `InternalServerError` |
| no response | `APIConnectionError` (`APIConnectionTimeoutError` on timeout) |
Every class is exported by name and as a static on `PlungeAI`. `err.code` is the envelope's `error.code`; `err.requestID` is the `x-request-id` header.
## Retries
Failed requests are retried 2 times by default with short exponential backoff (honouring `Retry-After`): connection errors, timeouts, 408, 429 and ≥ 500. 409 and 424 are never retried — on the One API they are semantic answers (`approval_required`, `duplicate_execution_id`, a missing connection), and a retried execute would start a second run. Set `maxRetries` on the client or per request:
```ts
const client = new PlungeAI({ maxRetries: 0 })
await client.agents.execute('calculator-agent', { prompt: '6*7' }, { maxRetries: 5 })
```
`idempotencyKey` (per request) is sent as `x-trace-id`, the API's idempotency key: it becomes the run's execution id, is re-sent on every retry of the call, and a replay answers 409 `duplicate_execution_id`.
## Timeouts
Requests time out after 10 minutes by default and throw `APIConnectionTimeoutError` (retried as above). Set `timeout` in milliseconds on the client or per request: `new PlungeAI({ timeout: 20_000 })`, `client.whoami({ timeout: 5_000 })`.
## Models plane
`client.chat.completions.create` (plus `client.embeddings` and `client.models`) covers the OpenAI-compatible routes here; the OpenAI SDK also works with `baseURL: 'https://api.plungeai.com/v1'` and the same key.
## Requirements
Node.js 20+, Deno, Bun, Cloudflare Workers and Vercel Edge. Browsers are refused unless you pass `dangerouslyAllowBrowser: true`, because a browser client exposes your `ozk_` key to anyone who loads the page.
# plungeai (Python)
Source: https://www.plungeai.com/docs/sdks/python
The official Python SDK for the PlungeAI One API (`https://api.plungeai.com`). Targets One API `2.5.0`; package version `1.0.0`. The resource methods and their types are generated from the API's own OpenAPI document (`GET /v1/openapi.json`). One runtime dependency: `httpx`.
Release status: `pip index versions plungeai`; until it answers, build from `sdk/one-api-python/` (`bash scripts/build.sh`, then `pip install target/sdk-py-build/*.whl`).
## Installation
```sh
pip install plungeai
```
## Usage
```python
from plungeai import PlungeAI
client = PlungeAI() # reads PLUNGEAI_API_KEY; or PlungeAI(api_key="ozk_…")
me = client.whoami()
run = client.agents.execute("calculator-agent", prompt="6*7")
hits = client.discovery.search(q="web search", kind="agents")
```
Method names are `snake_case`; request fields are keyword arguments typed as `TypedDict`s (`plungeai.types`); responses are plain `dict`s typed the same way. Path parameters come first, positionally: `client.workflows.versions.restore(workflow_id, version)`.
## Async client
```python
import asyncio
from plungeai import AsyncPlungeAI
async def main() -> None:
client = AsyncPlungeAI()
run = await client.agents.execute("calculator-agent", prompt="6*7")
async for event in await client.workflows.execute_inline_stream(workflow=workflow):
print(event.event, event.data)
asyncio.run(main())
```
Every method exists on both clients with the same signature.
## Streaming responses
```python
stream = client.workflows.execute_inline_stream(workflow=workflow)
for event in stream:
if event.event == "token":
print(event.json()["delta"], end="")
```
`event.data` is the raw string of the server-sent event; `event.json()` parses it. Leaving the loop closes the underlying response; on the async client an `AsyncStream` closes on `[DONE]`, end of body or an error, so pair an early `break` with `async with await client.agents.execute(..., stream=True) as stream:` (or `await stream.aclose()`). Dict and list query values are sent as JSON. Methods that take `stream=True` (`client.agents.execute`, `client.chat.completions.create`) return the same `Stream`.
## Auto-pagination
```python
for w in client.workflows.list(kind="bot"):
print(w)
page = client.workflows.list(limit=50)
for w in page.data:
print(w)
while page.has_next_page():
page = page.get_next_page()
for w in page.data:
print(w)
```
## Handling errors
```python
from plungeai import APIError, FailedDependencyError
try:
client.agents.execute("calculator-agent", prompt="6*7")
except FailedDependencyError as err:
print(err.connect["link"])
except APIError as err:
print(err.status_code, err.code, err.request_id)
```
| Status | Error class |
|---|---|
| 400 | `BadRequestError` |
| 401 | `AuthenticationError` |
| 403 | `PermissionDeniedError` |
| 404 | `NotFoundError` |
| 409 | `ConflictError` |
| 422 | `UnprocessableEntityError` |
| 424 | `FailedDependencyError` — `connect: { provider, method, link }` says where to connect the missing account |
| 429 | `RateLimitError` |
| ≥ 500 | `InternalServerError` |
| no response | `APIConnectionError` (`APITimeoutError` on timeout) |
Every class is importable from `plungeai`. `err.code` is the envelope's `error.code`; `err.request_id` is the `x-request-id` header.
## Retries
Failed requests are retried 2 times by default with short exponential backoff (honouring `Retry-After`): connection errors, timeouts, 408, 429 and ≥ 500. 409 and 424 are never retried — on the One API they are semantic answers (`approval_required`, `duplicate_execution_id`, a missing connection), and a retried execute would start a second run. Set `max_retries` on the client or per call:
```python
client = PlungeAI(max_retries=0)
client.agents.execute("calculator-agent", prompt="6*7", max_retries=5)
```
`idempotency_key` (per call) is sent as `x-trace-id`, the API's idempotency key: it becomes the run's execution id, is re-sent on every retry of the call, and a replay answers 409 `duplicate_execution_id`.
## Timeouts
Requests time out after 10 minutes by default and raise `APITimeoutError` (retried as above). Set `timeout` in seconds on the client or per call: `PlungeAI(timeout=20.0)`, `client.whoami(timeout=5.0)`.
## Raw responses
```python
response = client.agents.with_raw_response.execute("calculator-agent", prompt="6*7")
print(response.status_code, response.headers["x-request-id"])
run = response.parse()
```
`extra_headers` and `extra_query` on any call add to the request; `client.get(path, query=…)`, `client.post(path, body=…)` and friends reach routes the generated layer does not cover; `executions.continue` is spelled `client.executions.continue_(...)` (Python keyword).
## Models plane
`client.chat.completions.create(...)` (plus `client.embeddings` and `client.models`) covers the OpenAI-compatible routes here; the OpenAI SDK also works with `base_url="https://api.plungeai.com/v1"` and the same key.
## Requirements
Python 3.10 or newer. Type-checked with `mypy --strict`; the package ships `py.typed`.
# plungeai-go
Source: https://www.plungeai.com/docs/sdks/go
The official Go SDK for the PlungeAI One API (`https://api.plungeai.com`). Targets One API `2.5.0`; module version `1.0.0`. The resource methods and their types are generated from the API's own OpenAPI document (`GET /v1/openapi.json`). Standard library only.
Release status: `go list -m -versions github.com/PlungeAI/plungeai-go`; until it answers, use a `replace` directive to `sdk/one-api-go/` in the platform repository.
## Installation
```sh
go get github.com/PlungeAI/plungeai-go
```
## Usage
```go
import "github.com/PlungeAI/plungeai-go"
client := plungeai.NewClient() // reads PLUNGEAI_API_KEY; or plungeai.NewClient(plungeai.WithAPIKey("ozk_…"))
ctx := context.Background()
me, err := client.Whoami(ctx)
run, err := client.Agents.Execute(ctx, "calculator-agent", plungeai.AgentExecuteParams{Prompt: "6*7"})
hits, err := client.Discovery.Search(ctx, plungeai.DiscoverySearchParams{Q: "web search", Kind: "agents"})
```
Every method takes a `context.Context` first, then the path parameters, then a params struct (`plungeai.Params`), then optional request options. Optional bool and number request fields are pointers (`plungeai.Bool(false)`, `plungeai.Int(50)`, `plungeai.Float(0.2)`) so `false` and `0` reach the wire; optional strings are plain. Responses are pointers to generated structs; nested resources are fields (`client.Workflows.Versions.List(ctx, workflowID)`).
## Streaming responses
```go
stream, err := client.Workflows.ExecuteInlineStream(ctx, plungeai.WorkflowExecuteInlineStreamParams{Workflow: workflow})
if err != nil {
return err
}
defer stream.Close()
for stream.Next() {
ev := stream.Current()
if ev.Event == "token" {
var token struct{ Delta string `json:"delta"` }
_ = ev.JSON(&token)
fmt.Print(token.Delta)
}
}
if err := stream.Err(); err != nil {
return err
}
```
`ev.Data` is the raw string of the server-sent event; `ev.JSON(&v)` decodes it. Cancelling `ctx` stops the stream. Methods that accept `stream: true` on the API have a `…Streaming` twin (`client.Agents.ExecuteStreaming`, `client.Chat.Completions.CreateStreaming`) returning the same `*Stream`.
## Auto-pagination
```go
pager := client.Workflows.ListAutoPaging(ctx, plungeai.WorkflowListParams{Kind: "bot"})
for pager.Next() {
fmt.Println(pager.Current())
}
if err := pager.Err(); err != nil {
return err
}
page, err := client.Workflows.List(ctx, plungeai.WorkflowListParams{Limit: plungeai.Int(50)})
for err == nil {
for _, w := range page.Data {
fmt.Println(w)
}
if !page.HasNextPage() {
break
}
page, err = page.GetNextPage(ctx)
}
```
## Handling errors
```go
_, err := client.Agents.Execute(ctx, "calculator-agent", plungeai.AgentExecuteParams{Prompt: "6*7"})
var apiErr *plungeai.Error
if errors.As(err, &apiErr) {
switch apiErr.StatusCode {
case 424:
fmt.Println(apiErr.Connect.Link)
default:
fmt.Println(apiErr.StatusCode, apiErr.Code, apiErr.RequestID)
}
}
```
One error type, `*plungeai.Error`, carries `StatusCode`, `Code` (the envelope's `error.code`), `Message`, `RequestID` (the `x-request-id` header) and, on 424, `Connect` (`Provider`, `Method`, `Link` — where to connect the missing account). A request that never got a response returns the transport error (`errors.Is(err, context.DeadlineExceeded)` on timeout); a missing key returns `plungeai.ErrMissingAPIKey`.
## Retries
Failed requests are retried 2 times by default with short exponential backoff (honouring `Retry-After`): connection errors, timeouts, 408, 429 and ≥ 500. 409 and 424 are never retried — on the One API they are semantic answers (`approval_required`, `duplicate_execution_id`, a missing connection), and a retried execute would start a second run. Set the count on the client or per call:
```go
client := plungeai.NewClient(plungeai.WithMaxRetries(0))
client.Agents.Execute(ctx, "calculator-agent", plungeai.AgentExecuteParams{Prompt: "6*7"}, plungeai.WithMaxRetries(5))
```
`plungeai.WithIdempotencyKey(key)` is sent as `x-trace-id`, the API's idempotency key: it becomes the run's execution id, is re-sent on every retry of the call, and a replay answers 409 `duplicate_execution_id`.
## Timeouts
Each attempt times out after 10 minutes by default (retried as above). Set it on the client or per call: `plungeai.WithRequestTimeout(20 * time.Second)`. A deadline on `ctx` bounds the whole call, retries included.
## Raw responses
```go
var raw *http.Response
run, err := client.Agents.Execute(ctx, "calculator-agent", plungeai.AgentExecuteParams{Prompt: "6*7"}, plungeai.WithResponseInto(&raw))
fmt.Println(raw.StatusCode, raw.Header.Get("x-request-id"))
```
`plungeai.WithHeader(name, value)` and `plungeai.WithQuery(name, value)` add to any request (a `WithQuery` value overrides a same-named params field, as in the TypeScript SDK); `client.Get(ctx, path, params, &out)`, `client.Post(ctx, path, body, &out)` and friends reach routes the generated layer does not cover; the root `POST /v1/execute` is `client.ExecuteCall` (the name `Execute` is the generic request method).
## Models plane
`client.Chat.Completions.Create` (plus `client.Embeddings` and `client.Models`) covers the OpenAI-compatible routes here; any OpenAI client also works with base URL `https://api.plungeai.com/v1` and the same key.
## Requirements
Go 1.24 or newer. The module is published from the public repository `github.com/PlungeAI/plungeai-go`; its source of truth is `sdk/one-api-go/` in the platform repository.
# plungeai (Rust)
Source: https://www.plungeai.com/docs/sdks/rust
The official Rust SDK for the PlungeAI One API (`https://api.plungeai.com`). Targets One API `2.5.0`; crate version `1.0.0`. The resource methods and their types are generated from the API's own OpenAPI document (`GET /v1/openapi.json`). Async over `reqwest` (rustls) on the `tokio` runtime.
Release status: `cargo search plungeai`; until it answers, depend on the path `sdk/one-api-rust/` in the platform repository.
## Installation
```sh
cargo add plungeai tokio --features tokio/full
```
## Usage
```rust
use plungeai::{types::*, Client};
#[tokio::main]
async fn main() -> Result<(), plungeai::Error> {
let client = Client::new()?; // reads PLUNGEAI_API_KEY; or Client::builder().api_key("ozk_…").build()?
let me = client.whoami().await?;
let run = client
.agents()
.execute("calculator-agent", &AgentExecuteParams { prompt: Some("6*7".into()), ..Default::default() })
.await?;
let hits = client
.discovery()
.search(&DiscoverySearchParams { q: Some("web search".into()), kind: Some(DiscoverySearchParamsKind::Agents), ..Default::default() })
.await?;
Ok(())
}
```
Resources are methods on the client (`client.agents()`, `client.workflows().versions()`); every operation takes its path parameters first, then a reference to its params struct (`plungeai::types::Params`, all `Default`). Params and responses are `serde` structs under `plungeai::types`; optional fields are `Option`; response enums carry an `Unknown` variant, so a new server value never breaks a client.
## Streaming responses
```rust
use futures_util::StreamExt;
let mut stream = client
.workflows()
.execute_inline_stream(&WorkflowExecuteInlineStreamParams { workflow: workflow.into(), ..Default::default() })
.await?;
while let Some(event) = stream.next().await {
let event = event?;
if event.event.as_deref() == Some("token") {
let token: serde_json::Value = event.json()?;
print!("{}", token["delta"]);
}
}
```
`event.data` is the raw string of the server-sent event; `event.json::()` parses it. Dropping the stream cancels the request. Methods that accept `stream: true` on the API have a `…_stream` twin (`client.agents().execute_stream`, `client.chat().completions().create_stream`) returning the same `Stream`.
## Auto-pagination
```rust
let mut items = client.workflows().list(&WorkflowListParams { kind: Some(WorkflowListParamsKind::Bot), ..Default::default() }).await?.into_stream();
while let Some(w) = items.next().await {
println!("{:?}", w?);
}
let mut page = client.workflows().list(&WorkflowListParams { limit: Some(50), ..Default::default() }).await?;
loop {
for w in &page.data {
println!("{w:?}");
}
if !page.has_next_page() {
break;
}
page = page.next_page().await?;
}
```
Every `Page` carries `request_id` and `headers` from the response that produced it (also after `next_page()`); paged methods have a `list_raw` form for symmetry with the other `_raw` methods.
## Handling errors
```rust
match client.agents().execute("calculator-agent", &AgentExecuteParams { prompt: Some("6*7".into()), ..Default::default() }).await {
Ok(run) => println!("{run:?}"),
Err(plungeai::Error::Api(err)) if err.status == 424 => println!("{:?}", err.connect.map(|c| c.link)),
Err(plungeai::Error::Api(err)) => println!("{} {:?} {:?}", err.status, err.code, err.request_id),
Err(err) => return Err(err),
}
```
One enum, `plungeai::Error`: `Api(Box)` for a response with an error status (`status`, `code` — the envelope's `error.code`, `message`, `request_id` — the `x-request-id` header, and `connect` on 424 with `provider`, `method`, `link`), `Connection` for a request that never got a response, `Timeout`, and `Decode` for a body that does not match its type. Predicates `err.status()`, `err.is_not_found()`, `err.is_rate_limit()` and so on classify without matching.
## Retries
Failed requests are retried 2 times by default with short exponential backoff (honouring `Retry-After`, as seconds or an HTTP-date): connection errors, timeouts, 408, 429 and ≥ 500. 409 and 424 are never retried — on the One API they are semantic answers (`approval_required`, `duplicate_execution_id`, a missing connection), and a retried execute would start a second run. Set the count on the builder or per call:
```rust
let client = Client::builder().max_retries(0).build()?;
client.with_options(RequestOptions::new().max_retries(5)).agents().execute("calculator-agent", ¶ms).await?;
```
`RequestOptions::new().idempotency_key(key)` is sent as `x-trace-id`, the API's idempotency key: it becomes the run's execution id, is re-sent on every retry of the call, and a replay answers 409 `duplicate_execution_id`.
## Timeouts
Each attempt — connect, headers and the non-streaming body read — times out after 10 minutes by default and fails with `Error::Timeout` (retried as above); streaming bodies are not time-limited. Set it on the builder or per call: `Client::builder().timeout(Duration::from_secs(20))`, `RequestOptions::new().timeout(Duration::from_secs(5))`.
## Raw responses
```rust
let response = client.agents().execute_raw("calculator-agent", ¶ms).await?;
println!("{} {:?}", response.status, response.request_id);
let run: AgentExecuteResponse = response.parse()?;
```
`client.with_options(RequestOptions)` (`header`, `query`, `timeout`, `max_retries`, `idempotency_key`) returns a client whose every call carries those options (a `query` value overrides a same-named params field, as in the TypeScript SDK), and every JSON operation has a `…_raw` form returning the response with its `status`, `headers`, `request_id` and `body` bytes. `client.get(path)`, `client.post(path, &body)` and friends reach routes the generated layer does not cover.
## Models plane
`client.chat().completions().create(...)` (plus `client.embeddings()` and `client.models()`) covers the OpenAI-compatible routes here; any OpenAI client also works with base URL `https://api.plungeai.com/v1` and the same key.
## Requirements
Rust 1.85 or newer (edition 2021); `tokio` runtime. TLS through rustls — no system OpenSSL needed.
# PlungeAI in Claude Code
Source: https://www.plungeai.com/docs/integrations/claude-code
Planned: not available yet. Tracked as TI-74.
## What this will do
One `claude mcp add` command, or a project `.mcp.json`. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# PlungeAI in Claude.ai and Claude Desktop
Source: https://www.plungeai.com/docs/integrations/claude-ai
Planned: not available yet. Tracked as TI-74.
## What this will do
Skill upload on claude.ai; mcp-remote bridge on Desktop. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# PlungeAI in Codex
Source: https://www.plungeai.com/docs/integrations/codex
Planned: not available yet. Tracked as TI-74.
## What this will do
`codex mcp add` with a bearer-token env var. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# PlungeAI in Cursor
Source: https://www.plungeai.com/docs/integrations/cursor
Planned: not available yet. Tracked as TI-74.
## What this will do
One-click deeplink or `~/.cursor/mcp.json`. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# PlungeAI in VS Code
Source: https://www.plungeai.com/docs/integrations/vscode
Planned: not available yet. Tracked as TI-74.
## What this will do
One-click install or `code --add-mcp`. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# PlungeAI in Gemini CLI
Source: https://www.plungeai.com/docs/integrations/gemini-cli
Planned: not available yet. Tracked as TI-74.
## What this will do
`~/.gemini/settings.json` with `httpUrl`. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# PlungeAI in OpenCode
Source: https://www.plungeai.com/docs/integrations/opencode
Planned: not available yet. Tracked as TI-74.
## What this will do
`opencode.json` remote MCP entry. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# PlungeAI in Windsurf
Source: https://www.plungeai.com/docs/integrations/windsurf
Planned: not available yet. Tracked as TI-74.
## What this will do
`~/.codeium/windsurf/mcp_config.json` snippet. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# PlungeAI in Replit
Source: https://www.plungeai.com/docs/integrations/replit
Planned: not available yet. Tracked as TI-74.
## What this will do
One-click install into Replit Agent. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# PlungeAI in Lovable
Source: https://www.plungeai.com/docs/integrations/lovable
Planned: not available yet. Tracked as TI-74.
## What this will do
Chat connector or app connector. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# PlungeAI in Bolt.new
Source: https://www.plungeai.com/docs/integrations/bolt
Planned: not available yet. Tracked as TI-74.
## What this will do
Custom MCP server in Connectors. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# PlungeAI in v0
Source: https://www.plungeai.com/docs/integrations/v0
Planned: not available yet. Tracked as TI-74.
## What this will do
Add MCP in the prompt form; the generated app cannot call MCP. This page is not written yet; the source below covers the same material today.
## Use this today
The agent skill that covers the same ground.
# Any other MCP client
Source: https://www.plungeai.com/docs/integrations/other-mcp-clients
Planned: not available yet. Tracked as TI-74.
## What this will do
URL, header and transport for clients not listed here. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Agentic payments (x402)
Source: https://www.plungeai.com/docs/integrations/agentic-payments
Planned: not available yet. Tracked as TI-74.
## What this will do
Pay x402 services in USDC through the payment agents, with approval. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# AWS Marketplace
Source: https://www.plungeai.com/docs/integrations/aws-marketplace
Planned: not available yet. Tracked as TI-45.
## What this will do
You will be able to subscribe to PlungeAI and pay through your AWS account. There is no AWS Marketplace listing today.
## Use this today
Top up the wallet and see spend in Dashboard → One API → Billing.
# Browser Use
Source: https://www.plungeai.com/docs/integrations/browser-use
Planned: not available yet. Tracked as TI-46.
## What this will do
A browser-agent integration will let a workflow read pages that need a logged-in browser. No Browser Use integration exists today.
## Use this today
The tool agents you can call today.
# Google Cloud Marketplace
Source: https://www.plungeai.com/docs/integrations/google-cloud-marketplace
Planned: not available yet. Tracked as TI-45.
## What this will do
You will be able to subscribe to PlungeAI and pay through your Google Cloud account. There is no Google Cloud Marketplace listing today.
## Use this today
Top up the wallet and see spend in Dashboard → One API → Billing.
# Google Gemini Enterprise
Source: https://www.plungeai.com/docs/integrations/google-gemini-enterprise
Planned: not available yet. Tracked as TI-46.
## What this will do
Gemini Enterprise will be able to use PlungeAI agents and tools as a grounding source. No Gemini Enterprise integration exists today.
## Use this today
Use PlungeAI from Gemini CLI over MCP today.
# Google Sheets
Source: https://www.plungeai.com/docs/integrations/google-sheets
Planned: not available yet. Tracked as TI-46.
## What this will do
A Google Sheets function will run a PlungeAI agent over a range of cells and write the answers back. No Sheets function exists today.
## Use this today
Run a registry agent over the One API.
# LiteLLM
Source: https://www.plungeai.com/docs/integrations/litellm
Planned: not available yet. Tracked as TI-74.
## What this will do
Use the models plane as an OpenAI-wire LiteLLM provider. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# n8n
Source: https://www.plungeai.com/docs/integrations/n8n
Planned: not available yet. Tracked as TI-44.
## What this will do
n8n nodes will run PlungeAI agents, tools and workflows inside an n8n flow. No n8n node exists today. An HTTP Request node that calls the One API is the nearest path; it is not tested end to end.
## Use this today
Run a workflow over the One API.
# OpenRouter compatibility
Source: https://www.plungeai.com/docs/integrations/openrouter
Planned: not available yet. Tracked as TI-74.
## What this will do
Endpoint and field parity with OpenRouter, and what you gain. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Render
Source: https://www.plungeai.com/docs/integrations/render
Planned: not available yet. Tracked as TI-46.
## What this will do
A Render template will deploy a sample app that calls PlungeAI in one click. No template exists today.
## Use this today
Generate a typed client for your own app from the OpenAPI spec.
# Superhuman
Source: https://www.plungeai.com/docs/integrations/superhuman
Planned: not available yet. Tracked as TI-46.
## What this will do
Superhuman users will be able to call PlungeAI skills from their inbox. No Superhuman integration exists today.
## Use this today
Connect any MCP-capable client to `https://mcp.plungeai.com/v1`.
# Vercel
Source: https://www.plungeai.com/docs/integrations/vercel
Planned: not available yet. Tracked as TI-74.
## What this will do
Vercel AI SDK against the models plane; v0 over MCP. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Zapier
Source: https://www.plungeai.com/docs/integrations/zapier
Planned: not available yet. Tracked as TI-44.
## What this will do
A Zapier app will run PlungeAI agents and workflows as Zap actions. No Zapier app exists today.
## Use this today
Run a workflow over the One API.
# Data Integrations
Source: https://www.plungeai.com/docs/data-integrations/overview
Planned: not available yet. Tracked as TI-43.
## What this will do
Data integrations will let Spark, DuckDB, BigQuery, Polars, Snowflake and Supabase run PlungeAI agents over the rows of a table or a dataframe. None exists today. The nearest path today is calling `POST /v1/agents/:id/execute` from a UDF or an edge function, or the models plane.
## Use this today
Planned.
Planned.
Planned.
Planned.
Planned.
Planned.
Enrich a list of rows with a workflow you can run today.
# Apache Spark
Source: https://www.plungeai.com/docs/data-integrations/spark
Planned: not available yet. Tracked as TI-43.
## What this will do
The Apache Spark integration will provide SQL-native UDFs that call PlungeAI agents. It does not exist yet. The nearest path today is calling `POST /v1/agents/:id/execute` from a UDF or an edge function, or the models plane.
## Use this today
Enrich a list of rows with a workflow you can run today.
# DuckDB
Source: https://www.plungeai.com/docs/data-integrations/duckdb
Planned: not available yet. Tracked as TI-43.
## What this will do
The DuckDB integration will provide batch enrichment from DuckDB queries. It does not exist yet. The nearest path today is calling `POST /v1/agents/:id/execute` from a UDF or an edge function, or the models plane.
## Use this today
Enrich a list of rows with a workflow you can run today.
# Google BigQuery
Source: https://www.plungeai.com/docs/data-integrations/bigquery
Planned: not available yet. Tracked as TI-43.
## What this will do
The Google BigQuery integration will provide BigQuery remote functions that call PlungeAI agents. It does not exist yet. The nearest path today is calling `POST /v1/agents/:id/execute` from a UDF or an edge function, or the models plane.
## Use this today
Enrich a list of rows with a workflow you can run today.
# Polars
Source: https://www.plungeai.com/docs/data-integrations/polars
Planned: not available yet. Tracked as TI-43.
## What this will do
The Polars integration will provide a Polars expression plugin that calls PlungeAI agents. It does not exist yet. The nearest path today is calling `POST /v1/agents/:id/execute` from a UDF or an edge function, or the models plane.
## Use this today
Enrich a list of rows with a workflow you can run today.
# Snowflake
Source: https://www.plungeai.com/docs/data-integrations/snowflake
Planned: not available yet. Tracked as TI-43.
## What this will do
The Snowflake integration will provide a Snowflake UDTF that calls PlungeAI agents. It does not exist yet. The nearest path today is calling `POST /v1/agents/:id/execute` from a UDF or an edge function, or the models plane.
## Use this today
Enrich a list of rows with a workflow you can run today.
# Supabase
Source: https://www.plungeai.com/docs/data-integrations/supabase
Planned: not available yet. Tracked as TI-43.
## What this will do
The Supabase integration will provide Supabase Edge Function helpers that call PlungeAI agents. It does not exist yet. The nearest path today is calling `POST /v1/agents/:id/execute` from a UDF or an edge function, or the models plane.
## Use this today
Enrich a list of rows with a workflow you can run today.
# PlungeAI API Glossary
Source: https://www.plungeai.com/docs/resources/glossary
Planned: not available yet. Tracked as TI-74.
## What this will do
Every term used across the API, MCP and skills docs. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Connector catalog
Source: https://www.plungeai.com/docs/resources/connector-catalog
Planned: not available yet. Tracked as TI-74.
## What this will do
OAuth and API-key connectors, BYOK, and how connected accounts reach calls. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Data and privacy
Source: https://www.plungeai.com/docs/resources/data-and-privacy
This page covers the models plane: `/v1/chat/completions`, `/v1/completions` and `/v1/embeddings` on the inference gateway.
## What a request row holds
Every models-plane request writes one row to the `inference_requests` table and one data point to Cloudflare Workers Analytics Engine. The row holds metadata only:
| Column | What it is |
|---|---|
| `id` | The server-generated request id |
| `api_key_id`, `org_id` | The key that made the call and its organisation |
| `provider`, `model` | The provider and model that served the request |
| `model_requested` | The model or first candidate you asked for |
| `route_attempts`, `route_sort` | The routing trail: for each attempt the slug, provider, HTTP status, duration and reason; and the `sort` you sent |
| `stream` | Whether the response was streamed |
| `prompt_tokens`, `completion_tokens`, `total_tokens` | Token counts |
| `cache_read_tokens`, `cache_write_tokens`, `cache_savings_usd` | The provider prompt-cache share of the prompt tokens and what it saved |
| `cost_usd`, `price_usd`, `margin_usd` | Upstream cost, the price charged, and the difference |
| `ttft_ms`, `latency_ms` | Time to first token and total latency |
| `status`, `error_code` | HTTP status and error code |
| `byok` | Whether your own provider key was used |
| `client_request_id` | The `x-request-id` you sent, if any |
| `created_at` | When the request arrived |
No prompt text and no completion text is stored in the row. The Analytics Engine data point carries the organisation, provider, model, error code, BYOK flag, token counts, cost, latencies and status, and no text either.
## Response cache
The exact-match response cache is off by default. An organisation opts in, and only a non-streamed request with an explicit `temperature: 0` is eligible. When it is on, the full response body is stored in Cloudflare KV under a SHA-256 hash of your organisation id and the request body. The cache lifetime is the organisation's setting (default 300 seconds), clamped to between 60 seconds and 24 hours. A hit never crosses an organisation boundary.
## Retention
- Request rows (`inference_requests`) have no scheduled deletion today: they are kept for billing and reconciliation until the account is erased. An account erasure request deletes the organisation's rows.
- Cached responses expire with the cache lifetime above, 24 hours at most.
- Sticky-routing pins (a hash of the conversation prefix and the provider that served it, never the text) expire after 300 seconds.
## Sub-processors
Your prompt and the completion pass through the upstream provider that serves the request. The gateway can route to the providers below; when you bring your own key (BYOK), your own agreement with that provider applies. The gateway runs in Cloudflare's San Jose region; each provider processes the request in its own infrastructure, under its own terms.
| Provider | Company | Status | Data-use policy | Trains on API traffic? |
|---|---|---|---|---|
| `openai` | OpenAI | Serving | [OpenAI enterprise privacy](https://openai.com/enterprise-privacy/) | No, by default (API data is not used for training unless the customer opts in) |
| `anthropic` | Anthropic | Serving | [Anthropic commercial terms](https://www.anthropic.com/legal/commercial-terms) | No (commercial terms) |
| `google` | Google (Gemini API) | Serving | [Gemini API terms](https://ai.google.dev/gemini-api/terms) | See vendor policy (depends on the billing tier) |
| `groq` | Groq | Serving | [Groq privacy policy](https://groq.com/privacy-policy/) | See vendor policy |
| `mistral` | Mistral AI | Serving | [Mistral AI legal terms](https://mistral.ai/terms) | See vendor policy |
| `deepseek` | DeepSeek | Serving | [DeepSeek](https://www.deepseek.com/) | See vendor policy |
| `xai` | xAI | Serving | [xAI privacy policy](https://x.ai/legal/privacy-policy) | See vendor policy |
| `together` | Together AI | Serving | [Together AI privacy policy](https://www.together.ai/privacy) | See vendor policy |
| `fireworks` | Fireworks AI | Serving | [Fireworks AI privacy policy](https://fireworks.ai/privacy-policy) | See vendor policy |
| `cerebras` | Cerebras | Serving | [Cerebras privacy policy](https://www.cerebras.ai/privacy-policy) | See vendor policy |
| `perplexity` | Perplexity | Listed in the router, currently not serving (account credits) | [Perplexity privacy policy](https://www.perplexity.ai/hub/legal/privacy-policy) | See vendor policy |
| `cohere` | Cohere | Serving | [Cohere privacy policy](https://cohere.com/privacy) | See vendor policy |
| `openrouter` | OpenRouter | Serving | [OpenRouter privacy policy](https://openrouter.ai/privacy) | See vendor policy (OpenRouter forwards to further providers) |
| `moonshot` | Moonshot AI | Serving | [Moonshot AI platform](https://platform.moonshot.cn/) | See vendor policy (endpoint `api.moonshot.cn`) |
| `deepinfra` | DeepInfra | Serving | [DeepInfra privacy policy](https://deepinfra.com/privacy) | See vendor policy |
| `novita` | Novita AI | Listed in the router, currently not serving (no platform key) | [Novita AI privacy policy](https://novita.ai/legal/privacy-policy) | See vendor policy |
| `nebius` | Nebius | Listed in the router, currently not serving (no platform key) | [Nebius privacy policy](https://nebius.com/privacy-policy) | See vendor policy |
Cloudflare hosts the gateway, its D1 databases, KV and Analytics Engine.
## Data processing agreement
A data processing agreement (DPA) is available on request from PlungeAI support.
## SOC 2
PlungeAI is not yet SOC 2 certified, and no SOC 2 audit has been completed. The gateway writes a nightly, hash-chained reconciliation record of the billing ledger against the request rows, kept as evidence for a future audit.
## Region
The gateway and every other PlungeAI Worker run placed in one region: San Jose (Cloudflare colo SJC). Upstream providers process requests in their own regions.
# Memory
Source: https://www.plungeai.com/docs/resources/memory
Planned: not available yet. Tracked as TI-74.
## What this will do
Long-term memory, and distilling sessions into skills. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# Roles and permissions
Source: https://www.plungeai.com/docs/resources/roles-and-permissions
Planned: not available yet. Tracked as TI-74.
## What this will do
Company and team roles, scopes and levels in the Dashboard. This page is not written yet; the source below covers the same material today.
## Use this today
Teams, companies and roles live in Dashboard → Team and Dashboard → Workspace.
# Trust fence and approvals
Source: https://www.plungeai.com/docs/resources/trust-fence
Planned: not available yet. Tracked as TI-74.
## What this will do
Key fences, the trust fence, the agent fence, mission bounds and model guardrails. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# API Error Codes and Warnings
Source: https://www.plungeai.com/docs/resources/warnings-and-errors
Planned: not available yet. Tracked as TI-74.
## What this will do
Every error envelope and code, the outcomes, and what to retry. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Webhook setup
Source: https://www.plungeai.com/docs/resources/webhook-setup
Planned: not available yet. Tracked as TI-25.
## What this will do
PlungeAI will call a URL you register when a workflow run finishes, signed so you can verify the sender, with retries on failure. Nothing sends outbound webhooks today.
## Use this today
Follow a run live over SSE, or start it with sync:false and poll the results route.
# Migrate from legacy execute aliases
Source: https://www.plungeai.com/docs/workflows/migration-guide
Planned: not available yet. Tracked as TI-74.
## What this will do
Move `/v1/execute` and `/v1/cnl/*` calls to `/v1/workflows/*`. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the One API developer guide this page will restate.
# Docs for AI agents
Source: https://www.plungeai.com/docs/resources/docs-for-agents
Partly available: the Docs MCP server, which will let your agent search and read these pages. Tracked as TI-35.
Every page under www.plungeai.com/docs has a markdown form, the whole site has an index, and one file tells an agent how to connect PlungeAI.
## /agents.md
[https://www.plungeai.com/docs/agents.md](/agents.md) is the setup file for coding agents: get an `ozk_` key from the user, connect the client to `https://mcp.plungeai.com/v1`, verify with `plungeai_whoami`, add the skills and follow the rules. Paste this prompt into any agent to run it:
```text
Use curl to read https://www.plungeai.com/docs/agents.md and perform the setup to connect PlungeAI
```
It is served as `text/markdown; charset=utf-8`, and it is not a sidebar page.
## llms.txt and llms-full.txt
| File | What it holds |
|---|---|
| [/llms.txt](/llms.txt) | `# PlungeAI`, a one-line description, then one bullet per page in sidebar order (`[Title](https://www.plungeai.com/docs/.md): description`), with ` (planned)` after pages that are not available yet; then `## OpenAPI Specs` and `## Optional` |
| [/llms-full.txt](/llms-full.txt) | Every page in full: `# `, a `Source:` line with the page URL, then the page body; API and MCP reference pages in a condensed form |
## Markdown twins
Append `.md` to any page URL, for example [/getting-started/quickstart.md](/getting-started/quickstart.md), or request the page with `Accept: text/markdown`:
```bash
curl -s -H 'Accept: text/markdown' https://www.plungeai.com/docs/getting-started/quickstart
```
A twin starts with a pointer to `/llms.txt`, then the page title and description, then the page source unchanged. Twins carry `X-Robots-Tag: noindex`, so search engines index the HTML page only.
## Docs MCP
A docs MCP server at `www.plungeai.com/docs/mcp` will let an agent search these docs and read a page by path. It is planned (TI-35) and answers with a JSON-RPC error until then. The product MCP server at `https://mcp.plungeai.com/v1` is a different server and is live.
What the docs MCP server will do.
## Other llms.txt files
| URL | Covers |
|---|---|
| https://api.plungeai.com/llms.txt | The One API guide |
| https://mcp.plungeai.com/llms.txt | Workflow authoring over MCP (the CNL skill) |
| https://skills.plungeai.com/llms.txt | The agent skills catalog |
# Status
Source: https://www.plungeai.com/docs/resources/status
## Status page
The inference gateway publishes a public status page at [https://gateway.plungeai.com/status](https://gateway.plungeai.com/status). It needs no API key. It shows one row per upstream provider the gateway can route to:
| Column | What it is |
|---|---|
| State | `operational`, `degraded` (under 95% of the last 24 hours' requests to that provider succeeded) or `outage` (the gateway has the provider marked out after repeated upstream failures and is routing around it) |
| Success (24 h) | The share of the last 24 hours' requests to that provider that did not end in a 5xx error |
| p50 TTFT ms (1 h) | The median time to first token over the last hour's requests that recorded one, in milliseconds |
A provider with no traffic in the window shows a dash. The same data is available as JSON at `https://gateway.plungeai.com/status.json`:
```json
{
"generated_at": "2026-10-14T18:00:00.000Z",
"providers": [
{ "provider": "openai", "state": "operational", "success_24h": 0.9998, "p50_ttft_ms_1h": 412 }
]
}
```
Both are aggregates across all customers. They never carry an organisation, key or request. The snapshot is recomputed at most once a minute.
## Readiness and liveness
| Endpoint | Answers |
|---|---|
| `GET https://gateway.plungeai.com/health` | Readiness. `200` when the gateway can serve, `503` when it cannot |
| `GET https://gateway.plungeai.com/healthz` | Liveness. Always `200` with a fixed body while the Worker runs; it touches nothing |
`/health` runs three checks at once and reports each one:
| Check | What it tests | On failure |
|---|---|---|
| `billing_db` | A query against the billing database that holds balances and request rows | `status: "down"`, HTTP 503 |
| `kv` | A read from the key-value store that holds rate-limit counters and routing state | `status: "down"`, HTTP 503 |
| `ratecard` | The age, in seconds, of the published rate card the gateway prices requests from | Older than 26 hours, or no publish time: `status: "degraded"`, HTTP 200 |
The body also lists `providers_out`: the providers currently marked out after upstream failures. A marked provider does not make the gateway unready; it shows `outage` on the status page.
```json
{
"status": "ok",
"service": "inference-gateway",
"env": "production",
"checks": {
"billing_db": { "ok": true, "ms": 4 },
"kv": { "ok": true, "ms": 2 },
"ratecard": { "ok": true, "age_s": 3600 }
},
"providers_out": []
}
```
## Other health endpoints
| Service | URL |
|---|---|
| One API | https://api.plungeai.com/health |
| MCP server | https://mcp.plungeai.com/health and https://mcp.plungeai.com/v1/info |
| Skills site | https://skills.plungeai.com/health |
| Landing site | https://plungeai.com/health |
## Reporting a problem
If the status page shows every provider operational and your requests still fail, contact PlungeAI support with the `x-request-id` response header of a failing request.
# FAQs
Source: https://www.plungeai.com/docs/resources/faqs
Planned: not available yet. Tracked as TI-74.
## What this will do
Answers to common platform, API, billing and security questions. This page is not written yet; the source below covers the same material today.
## Use this today
The section of the MCP developer guide this page will restate.
# One API guide
Source: https://www.plungeai.com/docs/one-api
The **Ocean One API** is a single HTTPS front door to the Ocean platform. One base URL,
one router, and every capability the platform exposes reachable from it:
https://api.plungeai.com
Everything in this guide is a real route on that host. The router is a thin Hono worker
(`orchestration/api-gateway/api-gateway.ts`) that fronts the registry, the CNL engine, the
MCP gateway, the MCP executor, and the inference gateway over internal service bindings —
you never address those services directly.
## Start here — one call, 30 seconds
Get an `ozk_` key at Dashboard → **One API → Keys** (`https://dashboard.plungeai.com`), then:
```bash
curl -s -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"prompt":"In one sentence: what is a content delivery network?","sync":true,"max_tokens":60}'
```
Add `"stream": true` and you get OpenAI-shaped chunks; send the same fields as YAML with
`format: markdown` and you get Markdown back. Thirty-five more pasteable calls — streaming and
not, JSON and YAML, tools, workflows, MCP, create-and-run — are in the [Quickstart](/one-api/quickstart).
## Three levels
The One API has three levels. All three stay: none is deprecated in favour of another, and they share the
one `ozk_` key, the request ids and the error envelope.
| Level | What it is | Use it when | Details |
|---|---|---|---|
| **1. The compatible wire** | OpenAI-shaped `POST /v1/chat/completions`, `/v1/embeddings`, `/v1/completions`, `GET /v1/models`, and the OpenRouter drop-in base `/api/v1`. Models only | you already have an OpenAI or OpenRouter client: swap the base URL | [Models plane](/one-api/models), [OpenRouter compatibility](/one-api/openrouter-compat) |
| **2. Standard REST** | resources with JSON bodies; YAML is accepted and returned on the same routes. Workflows (create, version, run), executions, schedules, triggers, bot policy and budgets, workspace, inbox, bindings, groups, memory, skills, templates, connections, tools, agents, discovery, traces | you manage things or call one by id: create a bot, cap its spend, answer a paused run, list schedules | the plane chapters, and [Bots](/one-api/bots) for the bot surface |
| **3. The Agent Call door** | `POST /v1/execute`: the body is the program. A bare task, a `workflow:`, or a saved agent `agent: [@n]`; always through the engine; `dry_run`, `stream`, and headers that say what ran | the body is the program: one call, no resource to create first | [The one door](/one-api/bots#169-the-one-door) |
**Start with level 2; use level 3 when the body is the program.** Level 2 gives every resource its own
route, status codes and ids, so it is the one to build on. Level 3 is one route that takes whatever you
send and runs it; it is also how an agent calls another agent by id. The same machine-readable contract
covers all three: `GET /v1/openapi.json` tags the operations `Compat` (level 1), `Workflows`, `Executions`,
`Schedules`, `Triggers`, `Bots`, `Memory`, `Skills`, `Templates`, `Connections` (level 2) and `Agent Call`
(level 3). The older level 2 planes (discovery, tools, agents, MCP, traces) and the meta routes carry no tag.
A level 2 call, read-only and live-verified:
```bash
curl -s "https://api.plungeai.com/v1/workflows?limit=1" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
## The seven planes
The router groups the routes of levels 1 and 2 into planes. Every plane takes the same `ozk_` platform key;
six are the **execution planes**, and the **models plane** is OpenAI-compatible. The resources that manage
bots (saved workflows, executions, schedules, triggers, permissions, memory, skills, templates, connections)
are level 2 as well and are in [Bots](/one-api/bots).
| Plane | What it does | Key routes | Key |
|---|---|---|---|
| **Models** | OpenAI-compatible chat, embeddings, model catalog — a transparent proxy to the inference gateway with ordered fallback, `sort`, `@preset/`, guardrails, and an opt-in response cache | `POST /v1/chat/completions` · `POST /v1/messages` (Anthropic wire, [3.16](/one-api/models#316-anthropic-messages-post-v1messages)) · `POST /v1/embeddings` · `GET /v1/models` · the same routes under `/api/v1` (OpenRouter drop-in, [5.0](/one-api/openrouter-compat#50-drop-in-mode)) | `ozk_` |
| **Tools** | List tool agents, read a tool's invocation contract, execute one operation behind a trust fence | `GET /v1/tools` · `GET /v1/tools/{id}` · `POST /v1/tools/{id}/execute` | `ozk_` |
| **Agents** | Execute a registry agent from a prompt — sync, async pointer, or OpenAI-shaped SSE | `GET /v1/agents` · `POST /v1/agents/{id}/execute` · `GET /v1/agents/results/{workflowId}/{taskId}` | `ozk_` |
| **Workflows** | Run a multi-agent CNL workflow, inline or saved, with optional SSE | `POST /v1/workflows/execute` · `POST /v1/workflows/execute-stream` · `POST /v1/workflows/{id}/execute` | `ozk_` |
| **MCP** | The platform *as* an MCP server (inbound Streamable HTTP), plus outbound MCP runs against catalog servers — full guide: [MCP developer guide](https://www.plungeai.com/docs/mcp) | `POST /v1/mcp` · `GET /v1/mcp/tools` · `POST /v1/mcp/runs` | `ozk_` |
| **Discovery** | Search and recommend across the capability registry (agents, skills, connectors, models, MCP servers, …) | `GET /v1/discovery/search` · `POST /v1/discovery/recommend` · `GET /v1/discovery/cards/{type}/{id}` | `ozk_` |
| **Traces** | Read the persisted execution trace (spans + gateway request log) for one trace id | `GET /v1/traces/{id}` | `ozk_` |
The full machine-readable route list and schemas are served at `GET /v1/openapi.json`
(source: `orchestration/api-gateway/openapi.ts`). There is also a signed, internal-only
capability plane that customer keys cannot open —
it is not one of the seven and is not documented here.
## Positioning
Think of the One API as **an OpenRouter-compatible models plane plus everything a model
router cannot do.** The models plane speaks the OpenAI wire format, so an existing OpenAI or
OpenRouter integration points at it with a base-URL swap (see [OpenRouter compatibility](/one-api/openrouter-compat)).
The execution planes are the part a router has no answer for: real tools with contracts and
a trust fence, registry agents, multi-agent workflows, discovery, MCP, and a persisted trace
for every run.
## One key
| Prefix | Opens | How you get it |
|---|---|---|
| `ozk_` | Every plane: models, tools, agents, workflows, MCP, discovery, traces, and the connector proxy | Your **Ocean account key**, at Dashboard → **One API → Keys** (`https://dashboard.plungeai.com`), or fetched by `ocean-cli login` / `oceancode login --provider ocean` after a browser sign-in; bound to your account and tier, and billed to the account's one wallet |
Everything is on `https://api.plungeai.com` (MCP also on `https://mcp.plungeai.com`). The
legacy `sk-ocean-` (models) and `sk-conn-` (connector proxy) keys are still accepted on their
own plane while integrations move over, no longer created in the Dashboard. A legacy key sent
to an execution plane is a `401` — see [Authentication & keys](/one-api/authentication). Full
key mechanics are in that chapter; this guide writes the key as the shell variable
`$PLUNGE_API_KEY` (`ozk_`).
## How a request flows
An execution-plane call is not a thin proxy to a model. The path is **router → CNL engine →
agent**:
1. The router authenticates the `ozk_` key, mints a server-side `x-request-id`, and either
adopts your UUID `x-trace-id` or generates the trace id (`api-gateway.ts`).
2. It dispatches over an internal service binding to the CNL engine, which loads the agent
card from the registry, refuses anything not `status:active` before dispatch, recalls
memory, and runs the agent.
3. The agent's result is stored in SharedMemory and returned; the whole run is persisted as
trace spans.
The engine hop exists on purpose: it gives every call the same history, outcome vocabulary
(`ok | needs_input | needs_connection | needs_api_key | needs_approval | unavailable |
error`), and trust fence, whether the caller is a workflow, Ocean Studio, or you over HTTP.
The rationale is recorded in `orchestration/api-gateway/log/API-SURFACE-DECISION-AND-PLAN.md`
(everything routes through the engine so there is one execution path, not two).
## What "live-verified" means in this guide
Every example that carries a hidden `# verify` / `# expect:` marker was run against the
deployed API and its output checked. The docs build strips the marker; a verifier
(`orchestration/api-gateway/docs/guide-3.0/verify-examples.mjs`) re-runs those examples with
one `ozk_` key. A markered chat example sends one small request (`max_tokens` ≤ 16); examples
that spend more, need a connected account or change state carry no marker and say so.
## 60-second tour
Set your key once, then hit three planes. These three are live-verified:
```bash
curl -s https://api.plungeai.com/health
```
```bash
curl -s "https://api.plungeai.com/v1/discovery/search?q=web+search&kind=agents&limit=3" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
```bash
curl -s "https://api.plungeai.com/v1/agents?limit=3" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
For a first tool call, agent call, workflow, and model completion, go straight to the
[Quickstart](/one-api/quickstart).
## What's new in 3.1
- **Three levels** — the compatible wire, standard REST in JSON or YAML, and the Agent Call door
`POST /v1/execute`; start with level 2 ([Three levels](#three-levels)).
- **The bot surface over REST** — a bot is a saved `kind: bot` workflow that runs on the `one-agent`
runtime. Save and version it, set its permissions and budgets, answer and approve its runs, schedule
and trigger it, read its workspace, inbox, bindings and groups, and use memory, skills and
templates ([Bots](/one-api/bots)). The same operations are MCP actions
([MCP developer guide](https://www.plungeai.com/docs/mcp)) and `ocean-cli` verbs.
## What's new in 3.0
- **Full field-level models reference** — every `/v1/chat/completions` field with its
support status ([Models plane](/one-api/models)).
- **OpenRouter compatibility matrix** — endpoint and field parity, same / differs / missing
/ Ocean-only ([OpenRouter compatibility](/one-api/openrouter-compat)).
- **Connectors chapter** — OAuth vs API-key connectors, connected accounts, the
connector proxy ([Connectors](/one-api/connectors)).
- **Agent tiers** — tool agents, `one-agent` (the runtime: call mode and loop mode),
`concierge-agent`, `claude-managed-agent` ([Agents plane](/one-api/agents)).
- **Capability injection** — skills, experts, persona, backgrounds, plugins, and MCP on
harness missions ([Skills, plugins & capability injection](/one-api/skills-plugins-capabilities)).
- **SDK matrix** — OpenAI SDKs, `@plungeai/one-api`, LiteLLM, Vercel AI SDK, LangChain, and
MCP clients ([SDKs & clients](/one-api/sdks-and-clients)).
- **Website copy** — this guide is also published at `https://plungeai.com/docs`.
## Where to get help
- **Docs:** `https://www.plungeai.com/docs/one-api` (this guide) · `GET /llms.txt` and
`GET /llms-full.txt` for LLM tooling · `GET /v1/openapi.json` for the contract.
- **Keys and support:** [Requests](https://www.plungeai.com/request/new?kind=question).
- **Every response** carries an `x-request-id` — include it when you report a problem
([Traces & observability](/one-api/traces-observability)).
# Quickstart
Source: https://www.plungeai.com/docs/one-api/quickstart
Thirty-five calls you can paste into a terminal and run as they are. Every `ozk_` sample
below is live-verified on the build date (the verifier runs the ones marked in the source;
the create/run/delete ones mutate state and are run by hand). Two minutes of setup:
1. Get an `ozk_` key: Dashboard → **One API → Keys** (`https://dashboard.plungeai.com`), copy it once.
2. Export it, and paste the four helpers once per terminal. `live` prints a stream's text as
it arrives (OpenAI-shaped chunks or engine token frames), `frames` prints the raw SSE,
`mcp` sends one MCP call, `mcptext` prints its rendered answer.
```bash
# prelude
export PLUNGE_API_KEY=ozk_… # paste your key here once per terminal
live() { python3 -u -c '
import sys, json
for line in iter(sys.stdin.readline, ""):
if not line.startswith("data:"): continue
b = line[5:].strip()
if b == "[DONE]": break
try: d = json.loads(b)
except Exception: continue
t = ((d.get("choices") or [{}])[0].get("delta") or {}).get("content") if "choices" in d else d.get("delta")
if t: sys.stdout.write(t); sys.stdout.flush()
print()'; }
frames() { grep --line-buffered -E '^(event|data|id):'; }
mcp() { curl -sN https://api.plungeai.com/v1/mcp -H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d "$1"; }
mcptext() { python3 -c 'import sys,json;raw=sys.stdin.read();fr=[l[5:].strip() for l in raw.splitlines() if l.startswith("data:")];d=json.loads(fr[-1] if fr else raw);r=d.get("result") or d;print(r["content"][0]["text"] if isinstance(r,dict) and r.get("content") else json.dumps(r,indent=1))'; }
uuid() { grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' | head -1; }
```
The wires are independent: send JSON or YAML, get `json`, `yaml`, `markdown` or `text` back
(`format` in the body, `?format=` on the URL, or an `Accept` header — see
[Workflows plane § response formats](/one-api/workflows)). Streams have one fixed shape per
plane. The same `$PLUNGE_API_KEY` opens every plane, the models plane (OpenAI SDK) included,
which is at the end of this chapter.
## 1.1 Your first three calls
An agent, synchronous, JSON in → JSON out:
```bash
curl -s -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"prompt":"In one sentence: what is a content delivery network?","sync":true,"max_tokens":60}'
```
The same agent, **streamed** — OpenAI-shaped chunks, ends with `data: [DONE]`:
```bash
curl -sN -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"prompt":"Write two markdown bullets about glaciers.","provider":"openai","model":"gpt-4o-mini","max_tokens":100,"stream":true}'
```
The same agent, **YAML in → Markdown out**:
```bash
curl -s -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @- <<'EOF'
prompt: "In two markdown bullets, what does a content delivery network do? Bold the key term in each."
provider: openai
model: gpt-4o-mini
maxTokens: 150
sync: true
format: markdown
EOF
```
The same first call in Python and TypeScript (plain HTTP, no SDK):
```python
import requests
r = requests.post(
"https://api.plungeai.com/v1/agents/one-agent/execute",
headers={"Authorization": "Bearer "},
json={"prompt": "In one sentence: what is a content delivery network?", "sync": True, "max_tokens": 60},
)
print(r.json()["content"])
```
```ts
const r = await fetch('https://api.plungeai.com/v1/agents/one-agent/execute', {
method: 'POST',
headers: { Authorization: 'Bearer ', 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt: 'In one sentence: what is a content delivery network?', sync: true, max_tokens: 60 }),
})
console.log((await r.json()).content)
```
## 1.2 YAML in → Markdown out (10)
**A1** — direct agent call, `sync: true` + `format: markdown`. The body is the Markdown itself,
then `---` and an `execution:` footer with the run id.
```bash
curl -s -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @- <<'EOF'
prompt: "Reply with exactly this markdown line: **yaml-md-ok**"
provider: openai
model: gpt-4o-mini
maxTokens: 24
sync: true
format: markdown
EOF
```
**A2** — `format: text`: the bare content, no footer.
```bash
curl -s -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @- <<'EOF'
prompt: "Reply with exactly this markdown line: **yaml-md-ok**"
provider: anthropic
model: claude-sonnet-4-5-20250929
maxTokens: 24
sync: true
format: text
EOF
```
**A3** — no `format:` in the body; ask with the `Accept` header. Precedence: body `format` →
`?format=` → `Accept` → mirror of the request Content-Type (YAML in, YAML out).
```bash
curl -s -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' -H 'Accept: text/markdown' --data-binary @- <<'EOF'
prompt: "Reply with exactly this markdown line: **yaml-md-ok**"
provider: gemini
maxTokens: 24
sync: true
EOF
```
**A4** — `stream: true`, Markdown streamed live. `format:` never applies to a stream; the
Markdown is what the model writes and `live` joins the `delta.content` pieces (what Studio
and the CLI do).
```bash
curl -sN -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @- <<'EOF' | live
prompt: "Reply with exactly: stream-ok"
provider: openai
model: gpt-4o-mini
maxTokens: 24
stream: true
EOF
```
**A5** — the same stream as raw frames.
```bash
curl -sN -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @- <<'EOF' | frames
prompt: "Name two lakes in one markdown bullet each."
provider: openai
model: gpt-4o-mini
maxTokens: 80
stream: true
EOF
```
**A6** — a tool, `operation:` + its fields bare at the root → a Markdown document (Brave news).
(`params:` still wraps them if you prefer.)
```bash
curl -s -X POST https://api.plungeai.com/v1/tools/brave-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @- <<'EOF'
operation: news
query: "Cloudflare Workers"
freshness: pw
count: 3
format: markdown
EOF
```
**A7** — a tool → Markdown (weather forecast). Requires nothing beyond the key.
```bash
curl -s -X POST https://api.plungeai.com/v1/tools/weather-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @- <<'EOF'
operation: forecast
params:
lat: 43.697
lon: -114.352
units: imperial
cnt: 8
format: markdown
EOF
```
**A8** — a whole workflow document with `format: markdown` and `input:` → the **result** as
Markdown. `format:`, `input:` and `inputs:` next to `workflow:` are the envelope fields (the
same ones Studio sends); `{input}` is substituted. Without `format:` you get the YAML pointer
envelope (ids only).
```bash
curl -s -X POST https://api.plungeai.com/v1/workflows/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @- <<'EOF'
format: markdown
input: "wf-md-ok"
workflow:
name: md-inline
tasks:
- type: task
id: t1
agent: one-agent
maxTokens: 40
prompt: "Reply with exactly this markdown line: **{input}**"
EOF
```
**A9** — the same workflow on the stream. The gateway follows the provider's `stream_url` for
you and injects the deltas as `event: token` frames — one connection. Pipe through `frames`
to see the named engine events around them.
```bash
curl -sN -X POST https://api.plungeai.com/v1/workflows/execute-stream \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @- <<'EOF' | live
input: "wf-md-ok"
workflow:
name: md-stream
tasks:
- type: task
id: t1
agent: one-agent
provider: openai
model: gpt-4o-mini
maxTokens: 40
prompt: "Reply with exactly this markdown line: **{input}**"
EOF
```
**A10** — MCP: the YAML travels inside the JSON-RPC frame; the answer is Markdown by default.
```bash
mcp '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_execute_workflow","arguments":{"user_request":"yaml in, markdown out","mode":"sync","input":"wf-md-ok","workflow_yaml":"workflow:\n name: md-mcp\n tasks:\n - type: task\n id: t1\n agent: one-agent\n maxTokens: 40\n prompt: \"Reply with exactly this markdown line: **{input}**\""}}}' | mcptext
```
## 1.3 JSON in → JSON out (10)
**B1** — agent, sync → JSON envelope (`content`, `workflow_id`, `task_id`, `request_id`).
```bash
curl -s -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"prompt":"In one sentence: what is a Cloudflare Worker?","sync":true,"max_tokens":60}' | python3 -m json.tool
```
**B2** — the generation knobs: `provider`, `model`, `temperature`, `max_tokens`. The Markdown
sits inside the JSON `content` string.
```bash
curl -s -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"prompt":"List three facts about the moon as a markdown list.","provider":"openai","model":"gpt-4o-mini","temperature":0,"max_tokens":120,"sync":true}' | python3 -m json.tool
```
**B3** — `stream: true` → OpenAI-shaped chunk stream, raw. An OpenAI SDK streaming client
works against this URL.
```bash
curl -sN -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"prompt":"Write two markdown bullets about glaciers.","provider":"openai","model":"gpt-4o-mini","max_tokens":100,"stream":true}' | frames
```
**B4** — the same stream, text only, live.
```bash
curl -sN -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"prompt":"Reply with exactly: json-stream-ok","provider":"openai","model":"gpt-4o-mini","max_tokens":24,"stream":true}' | live
```
**B5** — a tool, JSON body (`operation` + `params`) → JSON envelope.
```bash
curl -s -X POST https://api.plungeai.com/v1/tools/exa-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"operation":"search","params":{"query":"latest developments in quantum computing","numResults":3,"searchType":"auto"}}' | python3 -m json.tool | head -40
```
**B6** — a tool's invocation contract (operations, params, gates, examples).
```bash
curl -s https://api.plungeai.com/v1/tools/brave-agent -H "Authorization: Bearer $PLUNGE_API_KEY" | python3 -m json.tool | head -60
```
**B7** — a workflow as a JSON object, sync → JSON pointer, then the result as JSON.
```bash
R=$(curl -s -X POST https://api.plungeai.com/v1/workflows/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"workflow":{"name":"json-wf","tasks":[{"type":"task","id":"t1","agent":"one-agent","maxTokens":60,"prompt":"Explain {input} in one sentence."}]},"input":"edge computing"}')
echo "$R" | python3 -m json.tool
WF=$(echo "$R" | python3 -c 'import sys,json;print(json.load(sys.stdin)["workflow_id"])')
curl -s "https://api.plungeai.com/v1/workflows/results/$WF/t1" -H "Authorization: Bearer $PLUNGE_API_KEY" | python3 -m json.tool
```
**B8** — a workflow JSON body on the stream → the engine's named SSE events, every `data:` is
JSON: `request_received → workflow_loaded → workflow_started → task_dispatched →
streaming_started → token… → task_completed → workflow_completed → workflow_result`.
```bash
curl -sN -X POST https://api.plungeai.com/v1/workflows/execute-stream \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"workflow":{"name":"json-stream","tasks":[{"type":"task","id":"t1","agent":"one-agent","provider":"openai","model":"gpt-4o-mini","maxTokens":80,"prompt":"Explain {input} in two markdown bullets."}]},"input":"edge computing"}' | frames
```
**B9** — discovery: hybrid search over the capability registry → JSON cards.
```bash
curl -s "https://api.plungeai.com/v1/discovery/search?q=web+search&kind=agents&limit=3" \
-H "Authorization: Bearer $PLUNGE_API_KEY" | python3 -m json.tool | head -40
```
**B10** — MCP with `format: "json"`: the tool text is a JSON document and `structuredContent`
carries the same object. MCP's `format` enum is `markdown | json`; the JSON-RPC exchange
arrives as SSE frames (progress first, the result last) — MCP does not token-stream.
```bash
mcp '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"plungeai_execute_agent","arguments":{"user_request":"json please","agent":"one-agent","prompt":"Reply with exactly: mcp-json-ok","format":"json"}}}' | sed -n 's/^data: //p' | tail -1 | python3 -m json.tool
```
## 1.4 Mixed wires (2)
**M1** — JSON in → Markdown out.
```bash
curl -s -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"prompt":"Two markdown bullets about tides.","provider":"openai","model":"gpt-4o-mini","max_tokens":100,"sync":true,"format":"markdown"}'
```
**M2** — YAML in → JSON out.
```bash
curl -s -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @- <<'EOF' | python3 -m json.tool
prompt: "One sentence about tides."
provider: openai
model: gpt-4o-mini
maxTokens: 60
sync: true
format: json
EOF
```
## 1.5 Run, then create and run — always streaming (10)
These create rows in your account (the same D1 rows Studio writes) and delete them at the end.
Run them top to bottom in one terminal: the ids flow from one step to the next.
**C1** — run an agent, streamed (Anthropic this time).
```bash
curl -sN -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @- <<'EOF' | live
prompt: "Reply with exactly: run-ok"
provider: anthropic
model: claude-sonnet-4-5-20250929
maxTokens: 24
stream: true
EOF
```
**C2** — CREATE a workflow (`plungeai_workflow create`, `kind: workflow`).
```bash
STAMP=$(date -u +%H%M%S)
R=$(mcp "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"plungeai_workflow\",\"arguments\":{\"user_request\":\"create\",\"action\":\"create\",\"kind\":\"workflow\",\"name\":\"Quickstart workflow $STAMP\",\"yaml\":\"workflow:\\n name: quickstart-workflow\\n tasks:\\n - type: task\\n id: t1\\n agent: one-agent\\n provider: openai\\n model: gpt-4o-mini\\n maxTokens: 160\\n prompt: \\\"Write three markdown bullets about {input}.\\\"\"}}}")
echo "$R" | mcptext
WF_ID=$(echo "$R" | mcptext | uuid); echo "WF_ID=$WF_ID"
```
**C3** — run it, streamed as text.
```bash
curl -sN -X POST "https://api.plungeai.com/v1/workflows/$WF_ID/execute-stream" \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' -d '{"input":"the Rhine"}' | live
```
**C4** — CREATE a bot (`kind: bot`).
```bash
R=$(mcp "{\"jsonrpc\":\"2.0\",\"id\":4,\"method\":\"tools/call\",\"params\":{\"name\":\"plungeai_workflow\",\"arguments\":{\"user_request\":\"create\",\"action\":\"create\",\"kind\":\"bot\",\"name\":\"Quickstart bot $STAMP\",\"yaml\":\"workflow:\\n name: quickstart-bot\\n tasks:\\n - type: task\\n id: t1\\n agent: one-agent\\n provider: openai\\n model: gpt-4o-mini\\n maxTokens: 160\\n prompt: \\\"You are a friendly bot. Answer in markdown: {input}\\\"\"}}}")
echo "$R" | mcptext
BOT_ID=$(echo "$R" | mcptext | uuid); echo "BOT_ID=$BOT_ID"
```
**C5** — run the bot, streamed as text.
```bash
curl -sN -X POST "https://api.plungeai.com/v1/workflows/$BOT_ID/execute-stream" \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"input":"What should I pack for a week in Iceland in October?"}' | live
```
**C6** — CREATE an agentic agent (`kind: agent`): a harness mission on `one-agent` with
a tool fence and an iteration cap.
```bash
R=$(mcp "{\"jsonrpc\":\"2.0\",\"id\":5,\"method\":\"tools/call\",\"params\":{\"name\":\"plungeai_workflow\",\"arguments\":{\"user_request\":\"create\",\"action\":\"create\",\"kind\":\"agent\",\"name\":\"Quickstart agentic agent $STAMP\",\"yaml\":\"workflow:\\n name: quickstart-agentic-agent\\n tasks:\\n - type: harness\\n id: run\\n agent: one-agent\\n goal: \\\"Explain {input} in three markdown bullets, then call task_complete with the bullets.\\\"\\n mission: \\\"You are a concise explainer. Answer in Markdown only.\\\"\\n allowed_tools: [task_complete]\\n max_iterations: 3\"}}}")
echo "$R" | mcptext
AGENT_ID=$(echo "$R" | mcptext | uuid); echo "AGENT_ID=$AGENT_ID"
```
**C7** — run the agentic agent, streamed. A harness run streams its engine events
(`task_dispatched → task_completed → workflow_completed`); read the final Markdown from the
results route with the `workflow_id` in the frames.
```bash
curl -sN -X POST "https://api.plungeai.com/v1/workflows/$AGENT_ID/execute-stream" \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"input":"how a harness bounds an agent run"}' | frames
```
**C8** — BUILD a workflow from a goal (`plungeai_build_workflow` — the LLM writes the CNL).
```bash
R=$(mcp '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"plungeai_build_workflow","arguments":{"user_request":"build it","goal":"Search the web for the latest Cloudflare Workers news and summarise the top three items in markdown bullets"}}}')
echo "$R" | mcptext
BUILT_ID=$(echo "$R" | mcptext | uuid); echo "BUILT_ID=$BUILT_ID"
```
**C9** — run the built workflow, streamed.
```bash
curl -sN -X POST "https://api.plungeai.com/v1/workflows/$BUILT_ID/execute-stream" \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' -d '{"input":"Cloudflare Workers"}' | frames
```
**C10** — list what you created, delete it, prove it is gone.
```bash
mcp '{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"plungeai_list_workflows","arguments":{"user_request":"mine","search":"Quickstart "}}}' | mcptext
for id in $WF_ID $BOT_ID $AGENT_ID $BUILT_ID; do
mcp "{\"jsonrpc\":\"2.0\",\"id\":8,\"method\":\"tools/call\",\"params\":{\"name\":\"plungeai_workflow\",\"arguments\":{\"action\":\"delete\",\"workflow_id\":\"$id\"}}}" | mcptext
done
mcp '{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"plungeai_list_workflows","arguments":{"user_request":"verify","search":"Quickstart "}}}' | mcptext
```
## 1.6 The models plane with the OpenAI SDK
The models plane is OpenAI-compatible and takes the same `ozk_` key as every other plane.
Point the official SDK at `https://api.plungeai.com/v1` and pass a `provider/model` slug from
`GET /v1/models`.
```python
from openai import OpenAI
client = OpenAI(api_key="", base_url="https://api.plungeai.com/v1")
resp = client.chat.completions.create(
model="anthropic/claude-sonnet-4-6",
messages=[{"role": "user", "content": "Reply with exactly the word: pong"}],
max_tokens=16,
)
print(resp.choices[0].message.content)
```
```ts
import OpenAI from 'openai'
const client = new OpenAI({ apiKey: process.env.PLUNGE_API_KEY, baseURL: 'https://api.plungeai.com/v1' })
const resp = await client.chat.completions.create({
model: 'anthropic/claude-sonnet-4-6',
messages: [{ role: 'user', content: 'Reply with exactly the word: pong' }],
max_tokens: 16,
})
console.log(resp.choices[0].message.content)
```
```bash
curl -s https://api.plungeai.com/v1/chat/completions \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"model":"anthropic/claude-sonnet-4-6","messages":[{"role":"user","content":"Reply with exactly the word: pong"}],"max_tokens":16}'
```
The response `model` is the slug that actually served the request — after a failover it can
differ from what you asked for. Add `"stream": true` for SSE. Ordered fallback (`models[]`),
`sort`, `@preset/`, guardrails and the response cache are in [Models plane](/one-api/models).
## 1.7 Request ids and traces
Every response echoes a server-minted `x-request-id`. Send your own UUID `x-trace-id` to
correlate several calls, and read the persisted trace at `GET /v1/traces/{id}`
([Traces & observability](/one-api/traces-observability)). An id none of your calls used — or another
account's — is `404 not_found`:
```bash
curl -s "https://api.plungeai.com/v1/traces/$(uuidgen)" -H "Authorization: Bearer $PLUNGE_API_KEY"
```
Where to next: [Tools plane](/one-api/tools) for contracts and the trust fence,
[Agents plane](/one-api/agents) for every body field, [Workflows plane](/one-api/workflows) for
the CNL primer, [Streaming](/one-api/streaming) for the SSE vocabulary, [MCP plane](/one-api/mcp) to
use the platform from Claude, Cursor or any MCP client.
# Authentication & keys
Source: https://www.plungeai.com/docs/one-api/authentication
Every route except `GET /health` and `GET /v1/openapi.json` needs a key. One `ozk_` platform
key opens every plane on `https://api.plungeai.com` (models, tools, agents, workflows, MCP,
discovery, traces and the connector proxy) and the MCP server on `https://mcp.plungeai.com`.
Two older prefixes, `sk-ocean-` and `sk-conn-`, are still accepted (legacy) on their own plane while
integrations move over; new integrations use `ozk_`.
## The one key
| Prefix | Name | Opens | Auth checked by |
|---|---|---|---|
| `ozk_` | Ocean account key | Every plane: models (`/v1/chat/completions`, `/v1/embeddings`, `/v1/models`), tools, agents, workflows, MCP, discovery, traces, and the connector proxy (`/v1/connect/:provider/proxy/*`) | `core/core-api-keys` — the execution planes through `orchestration/api-gateway/lib/auth.ts`, the models plane and the connector proxy through `inference/gateway/src/auth/api-key.ts` |
In this guide: `$PLUNGE_API_KEY` = `ozk_`.
Billing follows the account: a key carries your account id, so every metered call on it —
models, embeddings, the connector proxy — is charged to your account's one wallet, whichever
of your `ozk_` keys made it.
Legacy keys: `sk-ocean-` (models plane only) and `sk-conn-` (connector proxy only) keep working
on their own plane, are refused on the execution planes, and can no longer be created in the
Dashboard. Replace them with an `ozk_` key.
## Which routes accept which key
The router (`api-gateway.ts`) gates `/v1/discovery`, `/v1/tools`, `/v1/agents`,
`/v1/workflows`, `/v1/mcp`, and `/v1/traces` with `requireAuth`, which accepts **only** a
bearer that starts with `ozk_` (or a signed internal identity). The models plane and the
connector proxy are mounted **without** `requireAuth` — the inference gateway resolves the key
downstream, and it accepts the same `ozk_` key. A legacy `sk-ocean-` or `sk-conn-` key is
accepted on its own plane only, so the one mistake left is a legacy key on an execution plane,
which returns `401`.
An `ozk_` key on the models plane works (live-verified):
```bash
curl -s https://api.plungeai.com/v1/models \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
A legacy model-key shape on an execution plane → `401` (the router rejects any non-`ozk_`
bearer before dispatch; live-verified):
```bash
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://api.plungeai.com/v1/agents/one-agent/execute \
-H "Authorization: Bearer sk-ocean-EXAMPLE" \ # legacy model key
-H "Content-Type: application/json" \
-d '{"prompt":"hi"}'
```
The `401` body is the standard error envelope with `X-Error-Code: unauthorized`
(live-verified):
```bash
curl -s https://api.plungeai.com/v1/tools
```
## Header forms
Both header forms work on the execution planes; the models plane (`/v1/chat/completions`,
`/v1/embeddings`, `/v1/models`) takes `Authorization: Bearer` only — `X-API-Key` there is a
`401`, by design.
| Header | Planes | Example |
|---|---|---|
| `Authorization: Bearer ` | all | `-H "Authorization: Bearer $PLUNGE_API_KEY"` |
| `X-API-Key: ` | execution planes (`ozk_`); **not** the models plane | `-H "X-API-Key: $PLUNGE_API_KEY"` |
`X-API-Key` with an `ozk_` key is live-verified:
```bash
curl -s "https://api.plungeai.com/v1/tools?limit=1" \
-H "X-API-Key: $PLUNGE_API_KEY"
```
The same header on the models plane → `401` (live-verified):
```bash
curl -s -o /dev/null -w '%{http_code}\n' https://api.plungeai.com/v1/models \
-H "X-API-Key: $PLUNGE_API_KEY"
```
## How to obtain each key
| Key | Where | Notes |
|---|---|---|
| `ozk_` | Dashboard → **One API → Keys** (`https://dashboard.plungeai.com`) | Self-service; session-authenticated (`apps/ocean-dashboard/src/worker/routes/platform-keys.routes.ts`, `POST /api/platform-keys`). Name the key, pick an expiry (never / 7–365 days), copy it once — it is shown exactly once and stored hashed. Bound to your account and its tier; revoke any time from the same list. |
The tab shows your **Ocean account key** (prefix and last four characters; **Rotate** replaces
it) — the key the Playground, `ocean-cli` and OceanCode use — above any extra named keys you
create there.
The command-line clients get the account key through a browser sign-in, with no key to paste:
`ocean-cli login` and `oceancode login --provider ocean` listen on `127.0.0.1:` and open
`https://dashboard.plungeai.com/api/cli/authorize?port=&state=`. Once you are
signed in (any sign-in method), the Dashboard redirects a one-time code (valid 120 s) to
`http://127.0.0.1:/callback`, and the client exchanges it with
`POST https://dashboard.plungeai.com/api/cli/token` for the key. The key never appears in a
browser URL.
Existing `sk-ocean-` and `sk-conn-` keys are listed read-only in the same tab under **Legacy
keys**; each can be revoked there, and no new ones are created.
## Key fences (scoping)
Keys can be scoped so a leak is bounded.
| Fence | Key kind | Effect | Source |
|---|---|---|---|
| `allowed_tools` | `ozk_` | Restrict the key to a named set of tools | `orchestration/mcp-gateway/auth-middleware.ts` |
| `allowed_ips` | `ozk_` | Accept the key only from listed IPv4 addresses / CIDR blocks or exact IPv6 addresses; a call from elsewhere is a definitive `401` (no fallback) | `core/core-api-keys/index.ts` (`ipAllowed`, `verifyApiKeyRecord`) |
| `allowed_models` | `sk-ocean-` (legacy) | Restrict the key to a model allow-list; an off-list model is `403 model_not_allowed` | `inference/gateway/src/auth/api-key.ts` (`assertModelAllowed`) |
| `monthly_limit_usd` | `sk-ocean-` / `sk-conn-` (legacy) | Per-key monthly spend cap; exceeding it is `429 insufficient_quota` (inference) or `402 insufficient_balance` (connector) | `inference/gateway/src/auth/rate-limit.ts` |
`ozk_` key records are stored hashed only (`key:{sha256}` in KV, `core/core-api-keys`); a named
key is shown once at mint time. The account key's plaintext is also kept encrypted
(`ocean-compliance.user_credentials`) so the Dashboard can hand it to your signed-in clients.
## Tiers and rate limits
Execution-plane rate limiting is per-key (per user uuid), enforced by a Durable Object on
`POST` executions only — catalog `GET`s are free (`api-gateway.ts` calls `checkRateLimit`
for `POST` only). The limits below are the code values in
`orchestration/mcp-gateway/rate-limiter.ts`:
| Tier | Requests / minute | Requests / day |
|---|---|---|
| free | 30 | 1,000 |
| pro | 100 | 10,000 |
| enterprise | 300 | 100,000 |
Reaching the day limit pauses the key for **ten minutes** (`Retry-After` ≤ 600), after which its day
count starts over — a key is never shut out longer than that (2026-10-05); the minute limit waits out
the minute. A key with no tier is treated as `free`: the key resolver defaults it before the limiter sees it
(`orchestration/mcp-gateway/auth-middleware.ts` `validateApiKey`). A per-key `rate_limit` override
is **ignored** on these planes (`orchestration/api-gateway/lib/auth.ts` does not pass it to
`checkRateLimit`); it applies only on `https://mcp.plungeai.com/v1`.
The numbers above are the defaults. PlungeAI sets the per-tier numbers, or switches the limits off, in the operator console
(One API › Keys › Rate limits; KV `rate-limits:config`, read by the limiter within a minute); while they are off no call is
counted and none returns `429`, and the failed-authentication meter in 13.4 stays on.
The models plane has its own limiter — a default of **600 req/min per key**
(`RATE_LIMIT_RPM_DEFAULT`, `inference/gateway/src/routes/chat-completions.ts`), also
overridable per key.
A separate **failed-auth limiter** meters bad credentials at **20 attempts / minute per
client IP** (`auth-fail` tier in `rate-limiter.ts`; wired in `auth.ts`) so key enumeration
is bounded.
When you are limited, the response is `429` with a `Retry-After` header (seconds) and a
`rate_limited` (execution planes) or `rate_limit_exceeded` (models plane) error code.
## Request ids and trace correlation
- **`x-request-id`** is minted server-side on every request and echoed on the response
(`api-gateway.ts`). It is never trusted from the client. Include it when reporting a
problem. Live-verified — the header is present:
```bash
curl -sD - -o /dev/null https://api.plungeai.com/health | grep -i '^x-request-id'
```
- **`x-trace-id`** is yours to set for correlating several calls into one trace. It is
honoured **only if it is a UUID**; otherwise the router falls back to the request id. On an
execution route the trace id doubles as the execution/workflow primary key, so replaying a
UUID that was already used returns `409 duplicate_execution_id` — send a fresh UUID or omit
the header.
Responses also set `X-Content-Type-Options: nosniff`. Error responses additionally set
`X-Error-Code` (equal to `error.code`) so a client can branch on the header regardless of the
negotiated body format.
## CORS
The router answers CORS for any origin and exposes the response headers a browser client needs (`x-request-id`, `X-Error-Code`, `Retry-After`, …; `cors()` in `api-gateway.ts`), and the models plane sets
`access-control-allow-origin: *` with `authorization, content-type, x-request-id` allowed and
`x-request-id, x-correlation-id, x-ocean-cost-usd, x-ocean-dropped-params, x-generation-id` exposed
(`inference/gateway/src/index.ts`). Browser SDKs
work, but do not ship a secret key in client-side code.
## Internal HMAC identity (not for customers)
Service-to-service calls inside the platform authenticate with a signed internal identity
(`X-Internal-Signature`, HMAC over the request) instead of a key, verified before the `ozk_`
path in `auth.ts`. This is the mechanism Ocean Studio and the engine use to call each other;
it is not available to external developers and needs no action from you.
# Models plane
Source: https://www.plungeai.com/docs/one-api/models
The models plane is the OpenAI-compatible surface: `POST /v1/chat/completions`, `POST /v1/embeddings`, `GET /v1/models`. Base URL `https://api.plungeai.com`. Auth is your `ozk_…` account key in `Authorization: Bearer` (only: `X-API-Key` is a `401` on this plane, except on `/v1/messages`, [3.16](#316-anthropic-messages-post-v1messages)), billed to your account's wallet — see [Authentication & keys](/one-api/authentication). `api.plungeai.com/v1/*` is a transparent proxy (`orchestration/api-gateway/routes/models-proxy.ts`) to the inference gateway (`inference/gateway/src/`); the proxy adds no auth of its own — the inference gateway owns the key, wallet, and metering.
> Examples in this chapter that carry `# verify` are run against the deployed API with an `ozk_` key by `verify-examples.mjs`; the others are verified against the gateway source (`inference/gateway/src`).
## 3.1 Endpoints
| Method | Path | Body | Notes |
|---|---|---|---|
| POST | `/v1/chat/completions` | JSON | Sync or `stream:true` SSE. Full field reference in [3.2](#32-request-fields). Handler `routes/chat-completions.ts`. |
| POST | `/v1/messages` | JSON | Anthropic Messages wire on the same money path; `x-api-key` or Bearer. Sync or `stream:true` SSE ([3.16](#316-anthropic-messages-post-v1messages)). Handler `routes/messages.ts`. |
| POST | `/v1/completions` | JSON | Legacy text completion: `prompt` is one string (arrays answer `400 unsupported_parameter`); answered as `text_completion`, streamed or not. A chat request with one user message underneath — same routing, guardrails, cache and billing. Handler `routes/completions.ts`. |
| POST | `/v1/embeddings` | JSON | OpenAI, Mistral, Google and Cohere models ([3.9](#39-embeddings)). Handler `routes/embeddings.ts`. |
| GET | `/v1/models` | — | Aggregate catalog, OpenAI shape ([3.8](#38-get-v1models)). Requires a key. Handler `routes/models.ts`. |
| GET | `/v1/generation` | — | Usage and cost of one completion ([3.15](#315-account--generation-routes)). Handler `routes/account.ts`. |
| GET | `/v1/credits` | — | Lifetime credits bought and used ([3.15](#315-account--generation-routes)). |
| GET | `/v1/key` · `/v1/auth/key` | — | The calling key: label, limit, usage, rate limit ([3.15](#315-account--generation-routes)). |
| GET | `/v1/models/{author}/{slug}/endpoints` | — | The endpoint serving one model ([3.15](#315-account--generation-routes)). |
## 3.2 Request fields
The support words below are: **supported** (mapped and sent), **partial** (only part works — the cell says which), **ignored** (accepted, no effect), **rejected** (errors), **Ocean-only** (a field OpenAI does not define).
Every request routes to exactly one **provider shape**. openai-shape providers: **openai, groq, mistral, deepseek, xai, together, fireworks, cerebras, perplexity, openrouter, moonshot** (`routing/model-router.ts` `PROVIDERS`). For **openai-shape** providers the gateway forwards your body after one field split (`splitRequestFields`, `routing/params.ts`): Ocean directives (`models`, `sort`, `compress`) and OpenRouter-only fields (`provider`, `route`, `transforms`, `plugins`, `usage`, `reasoning`, `include_reasoning`, `debug`, `session_id`) are removed before the provider sees them (`reasoning.effort` is sent as `reasoning_effort`). The sampling knobs `top_k`, `min_p`, `top_a` and `repetition_penalty` are forwarded to the OpenAI-compatible hosts only: **OpenAI itself** never receives them, nor any other field it does not define, while the other openai-shape providers keep provider-specific extras and `top_k`. Those names are returned in the `x-ocean-dropped-params` response header ([3.6](#36-response-headers)) — except `min_p`, `top_a` and `repetition_penalty`, which OpenRouter also defines and which are removed without a mention, like the other OpenRouter-only fields. For **anthropic**, **google** and **cohere** the gateway builds a fresh provider body from the same split; a field the shape cannot take is dropped (**ignored**). Those names are returned in the `x-ocean-dropped-params` response header ([3.6](#36-response-headers)) — OpenRouter-only fields (`min_p`, `top_a`, `repetition_penalty` among them) are again removed without a mention.
### Core fields
| Field | openai-shape | anthropic | google | cohere | Source / note |
|---|---|---|---|---|---|
| `model` | supported | supported | supported | supported | `provider/model` slug or bare alias ([3.7](#37-providers--slugs)). Required unless `models`. |
| `models` (array) | Ocean-only | Ocean-only | Ocean-only | Ocean-only | Ordered fallback list; **stripped** before an openai-shape upstream. `routing/select.ts` `resolveCandidates` ([3.3](#33-routing-models-sort-failover)). |
| `sort` | Ocean-only | Ocean-only | Ocean-only | Ocean-only | `price` \| `latency` \| `throughput`; stripped before upstream. `routing/select.ts` `sortCandidates`. |
| `messages` | supported | supported | supported | supported | Required; empty → `400 invalid_request_error` "messages required". |
| `stream` | supported | supported | supported | supported | SSE ([Streaming](/one-api/streaming)). |
| `max_tokens` | supported | supported | supported | supported | Defaults to **4096** on every shape when both token caps are omitted (`DEFAULT_MAX_OUTPUT_TOKENS_ESTIMATE`). For reasoning models (`o1..o9`, `gpt-5*`) it is folded into `max_completion_tokens`. |
| `max_completion_tokens` | supported | supported | supported | supported | Preferred for reasoning models; on translated shapes it feeds the same output cap. |
| `temperature` | supported | supported | supported | supported | Mapped on every shape. |
| `top_p` | supported | supported (`top_p`) | supported (`topP`) | supported (`p`) | |
| `stop` | supported | supported (`stop_sequences`) | supported (`stopSequences`) | supported (`stop_sequences`) | String or array. |
| `n` | supported (passthrough) | ignored | ignored | ignored | Reserve is sized for `n` completions; only openai-shape returns multiple choices. |
| `stream_options` | supported (passthrough) | ignored | ignored | ignored | Controls `include_usage` for openai-shape; translated shapes **always** emit a final `usage` ([4](/one-api/streaming)). |
| `user` | supported (passthrough) | supported | ignored | ignored | anthropic → `metadata.user_id`. |
### Messages & content parts
| Field | openai-shape | anthropic | google | cohere | Source / note |
|---|---|---|---|---|---|
| content part `text` | supported | supported | supported | supported | |
| content part `image_url` | supported (passthrough) | partial | supported | ignored | anthropic forwards the raw OpenAI part **unconverted** (`toAnthropicBody` passes non-text parts as-is) — real Anthropic expects `{type:"image",source:…}` and may 400. google: a data URI → `inlineData`, an http(s) URL → `fileData`. cohere extracts text parts only. |
| content part `file` / `input_audio` | supported (passthrough) | partial | ignored | ignored | Forwarded raw to anthropic, dropped for google/cohere. |
| `name` (on a message) | supported (passthrough) | ignored | ignored | ignored | Dropped by every translator. |
| `role:"tool"` message | supported (passthrough) | supported (mapped) | supported (mapped) | ignored | anthropic: `tool_result` / `tool_use` blocks; the results of one call share one user turn. google: `functionResponse` / `functionCall` parts. cohere: text-only extraction. |
| `tool_call_id` | supported (passthrough) | supported (mapped) | supported (mapped) | ignored | anthropic: `tool_result` / `tool_use` blocks; the results of one call share one user turn. google: `functionResponse` / `functionCall` parts. cohere: text-only extraction. |
| `tool_calls` (assistant) | supported (passthrough) | supported (mapped) | supported (mapped) | ignored | anthropic: `tool_result` / `tool_use` blocks; the results of one call share one user turn. google: `functionResponse` / `functionCall` parts. cohere: text-only extraction. |
### Tools & structured output
| Field | openai-shape | anthropic | google | cohere | Source / note |
|---|---|---|---|---|---|
| `tools` | supported (passthrough) | supported (mapped) | supported (mapped) | ignored | anthropic → `{name,description,input_schema}`; google → `functionDeclarations`; **cohere drops tools** (`toCohereBody` emits none). |
| `tool_choice` | supported (passthrough) | supported | supported (mapped) | ignored | anthropic: `auto`, `none`, `required`→`any`, a named function→`tool`. google → `toolConfig`. |
| `parallel_tool_calls` | supported (passthrough) | ignored | ignored | ignored | |
| `response_format` | supported (passthrough) | supported | supported (mapped) | ignored | anthropic: `json_object` / `json_schema` through one forced synthetic tool; ignored when the request also sends `tools`. google → `responseMimeType` + `responseSchema`. Not mapped for cohere. |
A forced tool call on the Anthropic shape comes back as `tool_calls` with `finish_reason:"tool_calls"` and `arguments` `{"city":"Berlin"}` (live-verified 2026-10-04). The call needs about 33 completion tokens, above the 16-token cap of `# verify` fences, so it carries no marker:
```bash
curl -s "$API_BASE/v1/chat/completions" \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H "content-type: application/json" \
-d '{"model":"anthropic/claude-haiku-4-5","max_tokens":64,"messages":[{"role":"user","content":"Weather in Berlin?"}],"tools":[{"type":"function","function":{"name":"get_weather","parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}}],"tool_choice":{"type":"function","function":{"name":"get_weather"}}}'
```
A `json_schema` response on the Google shape. Gemini 2.5 counts its thinking tokens against `max_tokens`, so the call needs `512` and carries no `# verify` marker; `message.content` is the JSON string `{"a":1}`:
```bash
curl -s "$API_BASE/v1/chat/completions" \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H "content-type: application/json" \
-d '{"model":"google/gemini-2.5-flash","max_tokens":512,"messages":[{"role":"user","content":"Return a=1"}],"response_format":{"type":"json_schema","json_schema":{"name":"o","schema":{"type":"object","properties":{"a":{"type":"integer"}},"required":["a"]}}}}'
```
### Sampling & OpenRouter extensions
| Field | openai-shape | anthropic | google | cohere | Source / note |
|---|---|---|---|---|---|
| `top_k` | supported on openai-compatible hosts, **ignored** on OpenAI itself | supported | supported (mapped) | ignored | google → `topK`. |
| `presence_penalty`, `frequency_penalty` | supported (passthrough) | ignored | supported (mapped) | ignored | google → `presencePenalty` / `frequencyPenalty`. |
| `repetition_penalty` | supported on openai-compatible hosts, **ignored** on OpenAI itself | ignored | ignored | ignored | |
| `seed` | supported (passthrough) | ignored | supported (mapped) | ignored | google → `seed`. |
| `logit_bias`, `logprobs`, `top_logprobs` | supported (passthrough) | ignored | ignored | ignored | Forwarded verbatim on openai-shape; the upstream decides. |
| `min_p`, `top_a` | supported on openai-compatible hosts, **ignored** on OpenAI itself | ignored | ignored | ignored | |
| `prediction`, `modalities`, `verbosity`, `web_search_options` | supported (passthrough) | ignored | ignored | ignored | openai-shape passthrough only. |
| `reasoning`, `reasoning_effort` | supported (passthrough) | ignored | ignored | ignored | Forwarded to reasoning-capable openai-shape providers. On the [Agents plane](/one-api/agents) `reasoning_effort` is a first-class field; here it is passthrough only. |
| `usage` (request-level) | supported | supported | supported | supported | `usage:{include:true}` adds `usage.cost` to the response ([3.5](#35-response-object)); Ocean always meters. |
| `provider` | ignored | ignored | ignored | ignored | OpenRouter-only. Accepted and removed before the provider; use Ocean `models` / `sort` for routing. |
| `route` | ignored | ignored | ignored | ignored | OpenRouter-only. Accepted and removed before the provider; use Ocean `models` / `sort` for routing. |
| `transforms`, `plugins` | ignored | ignored | ignored | ignored | OpenRouter-only. Accepted and removed before the provider; use Ocean `models` / `sort` for routing. |
| `prompt` (legacy) | rejected | rejected | rejected | rejected | Chat endpoint requires `messages` — a body without them is `400` "messages required". |
## 3.3 Routing: models, sort, failover
`routing/select.ts` and the `runAttempts` loop in `routes/chat-completions.ts`.
- **`models[]`** — an ordered candidate list. Each entry is normalized (alias table + `provider/model` shape); an unresolvable entry is **dropped** (recorded as `reason:"unknown_model"`), not fatal, as long as one survives. If every entry is unknown → `404 model_not_found`.
- **`sort`** — reorders candidates: `price` (cheapest `input+output` rate first, from the rate card), `latency` (lowest rolling p50, unknown providers last), `throughput` (highest rolling tok/s first). No `sort` or `<2` candidates → input order preserved.
- **Attempt loop** — one candidate at a time: up to **2 attempts** per candidate (one retry on `429`/`5xx`/network), then **failover** to the next. A non-429 `4xx` is a **caller error** on that candidate — no retry, and the loop moves to the next candidate (a retired model id or a stale key on one provider does not sink the request); when no candidate succeeds, the first such error's status and body are returned, so a single-model request gets its own `4xx` unchanged. Exhausting every candidate → `502` "All routing candidates failed".
- **Outage exclusion** — a candidate that exhausts its retries against a real upstream failure is marked out for **30s** (KV `outage:`); subsequent requests skip it (`reason:"outage_excluded"`) until the window elapses.
- **Retry backoff** — honors a small upstream `Retry-After` (capped at 2s), else a flat 250ms.
- **Honest `model`** — the response's `model` is the slug that actually **served** the request (matters after failover), not the one you asked for, for anthropic/google/cohere and for the **non-stream** openai path. On the **openai-shape stream** the per-chunk `model` is left as the upstream reports it (not rewritten). The full ordered trail is persisted to `inference_requests.route_attempts`.
## 3.4 Presets
Set `model` to `@preset/` (`policy/presets.ts`). The gateway resolves a stored preset (org-specific row wins over a platform-global one) and merges it **before** candidate resolution. Precedence: **your explicit request fields always win** — preset `params_json` fills defaults, preset `routing_json.models`/`sort` apply only when you did not send `models`/`sort`; the decision log records `@preset/` as the requested model. Unknown or inactive slug → `404 preset_not_found`. Presets are provisioned by an operator (signed `/admin/presets`); there is no public create route.
## 3.5 Response object
Non-stream body is OpenAI `chat.completion`; stream chunks are `chat.completion.chunk` ([4](/one-api/streaming)).
| Field | Value |
|---|---|
| `id` | `chatcmpl-…` on every shape (openai passthrough keeps the upstream id; translators mint one). |
| `object` | `chat.completion` \| `chat.completion.chunk`. |
| `created` | Unix seconds (upstream's for openai passthrough; `Date.now()` for translated shapes). |
| `model` | The served slug (see honest-model note in [3.3](#33-routing-models-sort-failover)). |
| `provider` | canonical provider id that served the request — `openai`, `anthropic`, …; every response and every stream chunk |
| `choices[].message` / `.delta` | Standard OpenAI shape. |
| `choices[].finish_reason` | See vocabulary below. |
| `choices[].native_finish_reason` | the provider's own finish reason — `end_turn`, `STOP`, `TOOL_CALL`, …; on openai-shape providers it equals `finish_reason` |
| `usage` | `prompt_tokens`, `completion_tokens`, `total_tokens`, plus `prompt_tokens_details.cached_tokens` and Ocean's extension `prompt_tokens_details.cache_write_tokens` when a cache read/write occurred. |
| `usage.cost`, `usage.is_byok`, `usage.cost_details` | only with `usage:{"include":true}`: `cost` is the price charged in USD, `cost_details.upstream_inference_cost` the list cost at markup 1, split into `upstream_inference_prompt_cost` and `upstream_inference_completions_cost` |
**`finish_reason` per shape** (`mapAnthropicStop` / `mapGoogleStop`, `streaming/sse-translate.ts`):
| Upstream | openai | anthropic | google | cohere |
|---|---|---|---|---|
| normal end | `stop` | `end_turn`,`stop_sequence`→`stop` | `STOP`→`stop` | always `stop` |
| length cap | `length` | `max_tokens`→`length` | `MAX_TOKENS`→`length` | `stop` |
| tool call | `tool_calls` | `tool_use`→`tool_calls` | a `functionCall` part → `tool_calls` | `stop` |
| content filter | `content_filter` | (mapped to `stop`) | `SAFETY`,`RECITATION`→`content_filter` | `stop` |
**Usage & cache tokens** — Anthropic's `input_tokens` (which *excludes* cache) is normalized to OpenAI semantics: `prompt_tokens` includes the cached portion, and `prompt_tokens_details` breaks it out. Cached tokens bill at cache rates and are subtracted from full-rate input, never double-counted (`metering/pricing.ts computeCost`).
## 3.6 Response headers
| Header | When | Meaning |
|---|---|---|
| `x-request-id` | always | The inference gateway's request id — the same value as `error.request_id` and the id of your row in the request log. The client's own `x-request-id` is never trusted as an id. |
| `x-ocean-router-id` | via `api.plungeai.com` | The router's own id for the call (its request-log row); quote it to support next to `x-request-id`. |
| `x-correlation-id` | when you send `x-request-id` | Echoes your value. |
| `x-cache` | cache-eligible + org cache on | `hit` (served from cache, $0) or `miss` (stored for next time) ([3.11](#311-response-cache)). |
| `x-ocean-cost-usd` | non-stream chat completions | The settled price in USD, 6 decimals (`0.000000` on a cache hit). Not sent on a stream: its price exists only after the last chunk. |
| `x-ocean-dropped-params` | when the request carried fields the routed provider shape cannot take | comma-separated names that were removed (see [3.2](#32-request-fields)); absent otherwise |
| `content-type` | always | `application/json` or `text/event-stream`. |
The money-plane error body carries `request_id` inside the JSON envelope; note it does **not** set `X-Error-Code` (that header is execution-plane only — see [Errors, limits & headers](/one-api/errors-limits)).
## 3.7 Providers & slugs
Address a model as `provider/model` (e.g. `anthropic/claude-sonnet-4-6`). Bare aliases in `routing/model-router.ts` `ALIASES` auto-prefix: `gpt-4o`, `gpt-4o-mini`, `gpt-4.1`, `gpt-4.1-mini`, `o1`, `o1-mini`, `o3-mini`, `claude-opus-4-7`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `gemini-2.5-pro`, `gemini-2.5-flash`. Any other bare name (no `/`) → `404 model_not_found`.
| Provider id | Shape | Upstream |
|---|---|---|
| `openai` | openai | api.openai.com |
| `anthropic` | anthropic | api.anthropic.com/v1/messages |
| `google` | google | generativelanguage.googleapis.com |
| `groq`, `mistral`, `deepseek`, `xai`, `together`, `fireworks`, `cerebras`, `perplexity`, `moonshot` | openai | each provider's OpenAI-compatible endpoint |
| `openrouter` | openai | openrouter.ai/api/v1 |
| `cohere` | cohere | api.cohere.com/v2/chat |
**Discover the live catalog** with `GET /v1/models` ([3.8](#38-get-v1models)) or the discovery plane `GET /v1/discovery/search?kind=models` ([Discovery](/one-api/discovery)). Not every provider slug in the map is priced/active — the catalog is the source of truth.
## 3.8 GET /v1/models
Requires a key. Returns `{ "object":"list", "data":[ … ] }` built from the rate-card snapshot (`routes/models.ts`, `metering/pricing.ts listCatalog`). Each entry:
```json
{
"id": "anthropic/claude-sonnet-4-6",
"object": "model",
"created": 0,
"owned_by": "anthropic",
"context_length": 200000,
"max_output": 8192,
"pricing": { "in": 3.0, "out": 15.0 },
"capabilities": { "streaming": true, "tools": true, "vision": true, "embedding": false, "fim": false },
"name": "Anthropic: claude-sonnet-4-6",
"canonical_slug": "anthropic/claude-sonnet-4-6",
"architecture": { "modality": "text+image->text", "input_modalities": ["text","image"], "output_modalities": ["text"], "tokenizer": null, "instruct_type": null },
"pricing_per_token": { "prompt": "0.000003", "completion": "0.000015", "input_cache_read": "0.0000003", "input_cache_write": "0.00000375" },
"top_provider": { "context_length": 200000, "max_completion_tokens": 8192, "is_moderated": false },
"supported_parameters": ["temperature", "top_p", "max_tokens", "max_completion_tokens", "stop", "tools", "tool_choice", "response_format", "top_k"],
"per_request_limits": null
}
```
`pricing.in`/`.out` are USD **per 1M tokens**. `pricing_per_token` is the same price in OpenRouter's format (USD per token, decimal string); `supported_parameters` lists what the routed provider shape honours (tool parameters only for tool-capable models). `vision:false` means "not verified", not "verified absent"; `max_output:null` means unknown; `fim` is a name heuristic (`codestral`). This shape differs from OpenRouter's `/models` — see [OpenRouter compatibility](/one-api/openrouter-compat).
## 3.9 Embeddings
`POST /v1/embeddings` serves **openai**, **mistral**, **google** and **cohere** models (`routes/embeddings.ts`, `streaming/embeddings-translate.ts`); any other provider answers `400 unsupported_provider`. Body is standard OpenAI (`model`, `input` string or array, optional `encoding_format`: `float` or `base64`); the answer is always OpenAI's `{object:"list", data:[{embedding, index}], model, usage}`, whatever the provider speaks. When a provider reports no token count (google) the usage is estimated as characters/4. An upstream failure passes through with its own status. BYOK, guardrails, reserve/settle metering, and trial exemption apply exactly as for chat. The price is the rate card's per-token input price; `google/gemini-embedding-001` is served (live-verified 2026-10-04) but has no rate-card row yet, so `GET /v1/models` does not list it and no price is published for it.
## 3.10 Guardrails
Org- and key-scoped policies (`policy/guardrails.ts`), merged strictest-wins and enforced **before** any spend or upstream call. What a developer sees:
| Situation | Status | Code |
|---|---|---|
| Model/provider not on the allow-list (or key `allowed_models`) | `403` | `model_not_allowed` |
| Prompt matches a blocked-content regex | `403` | `content_blocked` |
| Provider requires your own key on this plan and none is connected | `402` | `byok_required` |
| Org day/week/month spend cap reached | `429` | `spend_cap_exceeded` |
Spend caps count inference **and** connector spend plus outstanding wallet holds. On a `models[]` request the allow-list/`require_byok` filters narrow the candidate list per entry; only if **none** survive do you get the error above.
## 3.11 Response cache
Exact-match, per-org opt-in, **default off** (`metering/response-cache.ts`). Eligibility: `stream:false` **and** an explicit `temperature:0` (an absent temperature defaults to 1 and is not eligible). The key is a SHA-256 of the **entire** request body scoped by org, so any differing field (tools, `response_format`, `seed`, …) is a different entry. A **hit** returns the stored body with `x-cache: hit`, bills **$0** (no wallet touch, no upstream call); a **miss** on an eligible request sets `x-cache: miss` and stores the response (TTL clamped 60s–24h). A hit can never cross an org boundary.
## 3.12 BYOK-or-billed
Connect your own provider key (BYOK) in the dashboard; it is stored AES-256-GCM per org (`routing/byok.ts`, table `inference_byok_keys` on `ocean-billing`). When a BYOK key exists for the winning candidate's provider, the gateway uses it and bills only the org's flat `byok_routing_fee` (`cost=0`, `price=byok_fee`). A **single-candidate** BYOK request with a **zero** routing fee skips the wallet entirely (no reserve/settle/ledger row); a multi-candidate request always reserves (the winner may not be the zero-fee one). Guardrail `require_byok` can force BYOK for named providers → `402 byok_required` if unmet.
## 3.13 Billing & metering
From `inference/CHARGING-README.md`. **Every request is metered; only `active` accounts ever pay** — an unknown/missing/stale status resolves to `trial` (metered, never debited).
| Status | Calls | Wallet | Monthly cap |
|---|---|---|---|
| `trial` | yes | never debited | exempt; a per-org **$10/mo** platform-cost allowance applies (`TRIAL_MONTHLY_CAP_USD`), then `429 insufficient_quota` |
| `active` | yes | reserve→settle at true price | enforced |
| `suspended` / `closed` | `401` at key resolution | — | — |
- **Price** = provider `cost × org markup` (`inference_org_settings`), computed from the rate-card components (`metering/pricing.ts`); component-based, per-1M normalized.
- **Reserve-then-settle** — a conservative max estimate is held before the upstream call and settled at the true price after (`metering/billing.ts`). A failure releases the hold (no charge). A near-empty wallet → `402 insufficient_balance`. An `active` key over its `monthly_limit_usd` → `429 insufficient_quota`.
- **RPM limit** applies to everyone (abuse protection, not billing): `429 rate_limit_exceeded` at `RATE_LIMIT_RPM_DEFAULT` (600/min default), atomic per key ([Errors, limits & headers](/one-api/errors-limits)).
**Posted prices.** A model with a posted Ocean price bills at that price whichever host serves it; `x-ocean-pricing` on every completion says `posted`, `passthrough` (cost × markup, above) or `byok`, and `x-ocean-provider` names the host. A posted price never exceeds the model maker's own list price; a published change reaches billing within 120 s; an aborted stream settles what streamed at the posted price. The public list: `GET /v1/pricing` (no key), live at https://plungeai.com/pricing/.
## 3.14 Not supported today
- No `/v1/responses` (Responses API), `/v1/messages/count_tokens` or Message Batches (the Anthropic Messages wire itself is [3.16](#316-anthropic-messages-post-v1messages)), `/v1/providers`, `/v1/images`, `/v1/audio`, `/v1/moderations` or `/v1/rerank`, and no `/v1/embeddings/models` list route.
- `/v1/embeddings` serves openai, mistral, google and cohere models only ([3.9](#39-embeddings)); any other provider is `400 unsupported_provider`.
- `/v1/completions` takes `prompt` as one string; an array is `400 unsupported_parameter`.
- `usage.completion_tokens_details.reasoning_tokens` is passed through only when an openai-shape provider sends it; the anthropic, google and cohere translations do not report it.
- A request that times out is not answered with `408`: a network failure fails over to the next candidate and ends as `502` when every candidate failed.
- A `service_tier` other than `auto` or `default` is `400 unsupported_service_tier` (live-verified 2026-10-04): the rate card prices the default tier only.
- The OpenRouter-only request fields `provider`, `route`, `transforms`, `plugins`, `include_reasoning`, `debug` and `session_id` are accepted and **ignored**: they are removed before the provider sees them, without a mention in `x-ocean-dropped-params`. Use Ocean `models` / `sort` / presets for routing. `min_p`, `top_a` and `repetition_penalty` reach the OpenAI-compatible hosts only (never OpenAI itself, anthropic, google or cohere).
- On **anthropic**, `n`, `seed`, `presence_penalty`, `frequency_penalty`, `logit_bias`, `logprobs` / `top_logprobs`, `parallel_tool_calls` and `reasoning_effort` are dropped (the Messages API has no such fields) and named in `x-ocean-dropped-params`; `response_format` is not enforced when the request also sends `tools`; images are forwarded without conversion (may `400`).
- On **google**, `n`, `logit_bias`, `logprobs` / `top_logprobs`, `parallel_tool_calls` and `reasoning_effort` are dropped and named in `x-ocean-dropped-params`, and so are `file` / `input_audio` content parts (images are mapped).
- On **cohere**, the translation is text-only: `tools`, `tool_choice`, `response_format`, `top_k`, `seed`, `presence_penalty` / `frequency_penalty` and images are dropped **without** a mention in `x-ocean-dropped-params` (Cohere tool calling is not built); the fields listed for google are dropped and named.
```bash
curl -s "$API_BASE/v1/chat/completions" \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H "content-type: application/json" \
-d '{"model":"openai/gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":8}'
```
The models plane takes the same `ozk_` key as every other plane; a missing or invalid key is `401 invalid_api_key` (a legacy `sk-ocean-` key is also accepted here). `GET /v1/models` enforces the same:
```bash
curl -s "$API_BASE/v1/models" -H "Authorization: Bearer $PLUNGE_API_KEY"
```
## 3.15 Account & generation routes
Four read-only routes answer for the calling key's own account. They live on `/v1/*` (OpenAI shape, key required) and `/api/v1/*` (OpenRouter shape; `/models/{author}/{slug}/endpoints` is public there, like OpenRouter's).
| Route | Answers | Read from |
|---|---|---|
| `GET /generation?id=` | one completion's usage and cost: `tokens_prompt`, `tokens_completion`, `native_tokens_cached`, `total_cost` (what you were charged, USD), `upstream_inference_cost`, `is_byok`, `generation_time`, `latency`. `id` is the `gen-…` id of an `/api/v1` response (also its `x-generation-id` header) or the `x-request-id` of any call. Another account's id answers `404 not_found`. | `inference_requests` |
| `GET /credits` | `{"data":{"total_credits","total_usage"}}` — lifetime USD bought and spent; zeros for an account with no wallet yet | `inference_org_balance` |
| `GET /key` (alias `/auth/key`) | `{"data":{"label","limit","usage","limit_remaining","is_free_tier","rate_limit":{"requests","interval"}}}` — `usage` is this UTC month's spend on this key | `inference_api_keys` (legacy `sk-ocean-`), the key record (`ozk_`) |
| `GET /models/{author}/{slug}/endpoints` | the one endpoint that serves a model, with per-token pricing | rate card |
A generation row is written after the response completes, so a lookup in the same second can answer 404 — retry after a moment.
```bash
curl -s "$API_BASE/api/v1/credits" -H "Authorization: Bearer $PLUNGE_API_KEY"
```
The `gen-` id of an `/api/v1` completion resolves through `GET /v1/generation` (live-verified 2026-10-04):
```bash
RID=$(curl -s "$API_BASE/api/v1/chat/completions" \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H "content-type: application/json" \
-d '{"model":"openai/gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":4}' \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')
sleep 3
curl -s "$API_BASE/v1/generation?id=$RID" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
## 3.16 Anthropic Messages (`POST /v1/messages`)
The Anthropic Messages wire on the same money path as `/v1/chat/completions` (routing, `models`/`sort` failover, guardrails, reserve and settle, the `x-ocean-*` headers of [3.6](#36-response-headers)). An Anthropic SDK or Claude Code points its base URL at the gateway and keeps its own request and response shapes. Handler `routes/messages.ts`; translation `ingress/anthropic-in.ts` / `ingress/anthropic-out.ts`. Also mounted at `/api/v1/messages` (OpenRouter's Anthropic skin, `fallbacks` up to 3).
- **Auth:** `x-api-key: ozk_…` (with any `anthropic-version`) or `Authorization: Bearer ozk_…`.
- **Request:** `model` (any slug, not only `anthropic/*`), `messages` (string or blocks: `text`, `image`, `tool_use`, `tool_result`, `thinking`, …), `system` (string or blocks), `max_tokens`, `stop_sequences`, `temperature`, `top_p`, `top_k`, `tools`, `tool_choice`, `metadata.user_id`, `thinking`, `stream`; plus Ocean's `models`, `sort`, `max_cost`.
- **`anthropic/*` models:** your body is forwarded as sent, with only `model` (the upstream id) and `max_tokens` (the capped value, [3.2](#32-request-fields)) set by the gateway — `cache_control`, thinking and server tools reach Anthropic unchanged.
- **Any other model:** translated to the chat-completions shape — `system` → a system message, `tool_use` → `tool_calls`, `tool_result` → `tool` messages, `tools[].input_schema` → `function.parameters`, `tool_choice` `any` → `required`, `stop_sequences` → `stop`, `metadata.user_id` → `user`. Blocks without a neutral form (thinking, server tools) are not sent.
- **Response:** an Anthropic `message` (`content` text/`tool_use` blocks, `stop_reason`, `usage` with `input_tokens` excluding cache reads/writes). With `stream:true` the SSE sequence is `message_start`, `content_block_start` / `content_block_delta` (`text_delta`, `input_json_delta`) / `content_block_stop`, `message_delta` (stop reason + usage), `message_stop`; a failure after the first byte ends in an `error` event.
- **Errors:** `{"type":"error","error":{"type":"","message":"…"},"request_id":"…"}` with the HTTP status of [Errors](/one-api/errors-limits) — `401` `authentication_error`, `402` `billing_error`, `429` `rate_limit_error`, `5xx` `api_error`, `504` `timeout_error`.
```bash
curl -s https://gateway.plungeai.com/v1/messages \
-H "x-api-key: $PLUNGE_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"openai/gpt-4o-mini","max_tokens":64,
"system":"Answer in one sentence.",
"messages":[{"role":"user","content":"What is a Durable Object?"}]}'
```
Not yet live-verified (not deployed at time of writing; drill `tests/live/inference/anthropic-ingress.sh`). `api.plungeai.com/v1/messages` answers once the One API models proxy forwards the route.
# Streaming
Source: https://www.plungeai.com/docs/one-api/streaming
Three surfaces stream over Server-Sent Events (SSE): the **models plane** (`/v1/chat/completions` with `stream:true`), the **workflows plane** (`/v1/workflows/execute-stream`), and the **agents plane** (`/v1/agents/:id/execute` with `stream:true`). The models plane speaks the OpenAI delta wire; the workflows plane speaks the CNL engine's named-event wire; the agents plane re-shapes an engine run into the OpenAI delta wire. All three send `Content-Type: text/event-stream` and frame events as `…\n\n`.
## 4.1 Models-plane SSE
Code: `routes/chat-completions.ts` (stream branch) + `streaming/sse-translate.ts` + `streaming/usage-meter.ts`.
**Wire format.** Each event is one `data: \n\n` line whose payload is a `chat.completion.chunk`, terminated by a literal `data: [DONE]\n\n`. The gateway injects **no** comment lines on `/v1`; under the OpenRouter drop-in base `/api/v1` it sends `: OPENROUTER PROCESSING` until the first byte ([5.0](/one-api/openrouter-compat#50-drop-in-mode)).
**Role primer.** Differs by provider shape:
| Shape | First chunk | How |
|---|---|---|
| openai-shape (openai, groq, mistral, deepseek, xai, together, fireworks, cerebras, perplexity, openrouter, moonshot) | whatever the upstream sends (typically `delta:{role:"assistant"}`) | each upstream `data:` chunk is re-emitted by `shapeOpenAIStream` with the served `model` and `provider`; comment lines and non-JSON lines pass through unchanged. |
| anthropic | `delta:{role:"assistant"}` injected by Ocean | `translateAnthropic` emits a primer in `start()`. |
| google, cohere | `delta:{role:"assistant"}` injected by Ocean | emitted on the first upstream frame. |
**Final `usage` chunk.**
| Shape | Final `usage` present? |
|---|---|
| anthropic, google, cohere | **always** — the translator emits a final chunk carrying `finish_reason` + `usage` (anthropic adds `prompt_tokens_details` when cached). |
| openai-shape | **only when you send `stream_options:{"include_usage":true}`** (or `usage:{"include":true}`) — the gateway itself asks OpenAI and DeepSeek for the usage chunk so billing never depends on you, and removes the usage-only chunk from the stream you receive when you did not ask. |
Ocean meters tokens on every stream from the upstream's own usage chunk (a passive `usage-meter` sits between the upstream and the shaper; Groq's `x_groq.usage` is read too); what you receive still depends on `stream_options`.
**`model` and `provider` on stream chunks.** Every chunk of every shape carries the served slug in `model` (also after a failover) and the canonical provider id in `provider`.
**Response headers:** `content-type: text/event-stream`, `cache-control: no-cache, no-transform`, `x-accel-buffering: no`, `x-request-id`.
**Failover vs mid-stream failure.** Failover between candidates is decided on the response **status**, before any SSE byte is read (`runAttempts`); once bytes are flowing there is no re-failover. A failure after that point travels in-band: the final chunk carries `error: { message, type: "upstream_error", code: "upstream_error", metadata: { provider_name, raw } }` and `finish_reason: "error"`, followed by `data: [DONE]` — for an Anthropic `error` event, a Gemini `error` object, or an upstream connection that dies mid-stream. An openai-shape upstream that sends its own `{error}` event passes it through as sent.
**Cancellation / abort.** The client's `AbortSignal` is forwarded to the upstream fetch, so hanging up stops the provider. An aborted stream is billed for what it streamed — the completion tokens seen so far (a character estimate when the provider never sent usage) plus the prompt estimate — logged with `status 499` / `error_code client_abort`, and the hold is **settled** at that amount, not released (a trial org is logged but never debited). The */5 sweeper is only a backstop for a Worker that dies. There is no gateway-imposed stream idle timeout on this plane beyond the Workers runtime and the upstream's own.
A streamed chat ends with `[DONE]` (live-verified 2026-10-04):
```bash
curl -s "$API_BASE/v1/chat/completions" \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H "content-type: application/json" \
-d '{"model":"openai/gpt-4o-mini","stream":true,"messages":[{"role":"user","content":"Reply OK"}],"max_tokens":8}'
```
## 4.2 Workflows-plane SSE
Code: `routes/workflows.ts` + `lib/stream-relay.ts` (`workflowsEncoder`) + engine `orchestration/cnl-engine/context.ts`.
Routes: `POST /v1/workflows/execute-stream` (inline YAML/JSON body), `POST /v1/workflows/:id/execute-stream` (saved workflow), and the legacy alias `POST /v1/cnl/execute-stream`. All require an `ozk_` key ([Authentication & keys](/one-api/authentication)). The relay **also drains** every provider sub-stream the run advertises and closes the execution row under `waitUntil`, so a client disconnect never aborts the run.
**Wire format.** Engine frames pass through **verbatim** as `event: \ndata: \n\n`. Provider token deltas are injected as `event: token\ndata: {"task_id":"…","delta":"…"}\n\n`. Token frames for a task are flushed **before** that run's terminal frame (ordering guarantee).
**Engine event vocabulary** (`CNLEventType`, `orchestration/cnl-engine/context.ts`):
`request_received`, `workflow_loaded`, `workflow_started`, `task_dispatched`, `task_completed`, `task_error`, `task_skipped`, `streaming_started`, `streaming_completed`, `output_delivered`, `parallel_start`, `parallel_complete`, `sequential_start`, `sequential_complete`, `dynamic_start`, `dynamic_generated`, `dynamic_complete`, `batch_start`, `batch_item_start`, `batch_item_complete`, `batch_progress`, `batch_complete`, `debate_start`, `debate_round_complete`, `debate_judgment`, `debate_complete`, `condition_evaluated`, `workflow_yielded`, `workflow_result`, `workflow_completed`, `workflow_error`, `heartbeat`.
Each `data` payload carries `workflow_id`, `execution_id`, `timestamp`, `type`, a nested `data` object, and `elapsed_ms`. The outcome frame is `workflow_completed` (carries `final_task_id`, `execution_summary`) or `workflow_error` (carries `error`); `workflow_result` is always the **last** frame, after the outcome — keep reading until it arrives.
**Keepalive.** The engine emits its own `heartbeat` events; the relay forwards them verbatim and additionally touches the execution row's `updated_at` every ~60s so a long run is not swept mid-flight. This plane does **not** inject comment-line keepalives (that is the agents plane, [4.3](#43-agents-plane-openai-shaped-stream)).
**Reading the final result.** After `workflow_result` (the last frame), fetch the stored output with `GET /v1/workflows/results/:workflowId/:taskId` (use the `final_task_id`) — see [Workflows](/one-api/workflows). While a run is in flight the results route returns `404 not_ready`.
## 4.3 Agents-plane OpenAI-shaped stream
Code: `routes/agents.ts` (stream branch) + `lib/stream-relay.ts` (`openaiEncoder`). Requesting the stream: set `"stream": true` in the JSON body. This runs the same one-task engine envelope as the sync path but with `force_streaming`, and the `openaiEncoder` re-shapes the run into an OpenAI `chat.completion.chunk` stream — so an OpenAI SDK's streaming client works against an Ocean agent. (For the CNL-native event stream of the same agent, use the workflows plane instead.)
**What the client sees** — only `data:` lines and comments, never engine `event:` lines (they are dropped by the encoder):
- `id`: `chatcmpl-` — derived from the execution id, stable across the stream.
- `model`: the served model — a caller-pinned `provider/model`, else the provider's default from its `/health`, else `/default`. **Never** the agent id.
- A primer chunk (`delta:{role:"assistant"}`) is emitted lazily on the first token.
- Content chunks: `delta:{content:"…"}`.
- Final chunk: `delta:{}` with `finish_reason` (`stop` or `length`) and, when the split is readable, a `usage` object; then `data: [DONE]\n\n`.
- **Keepalive**: a comment line `: OCEAN PROCESSING\n\n` roughly every 15s while idle (so proxies don't time out a slow first token).
**Error after the first byte.** Once SSE headers are committed, a run error is emitted as `data: {"error":{"message":…,"type":"server_error","code":"engine_error"}}` and the stream closes **without** `[DONE]` (matching OpenRouter's mid-stream-error behaviour). A rejection *before* the first byte is a normal JSON `502 engine_error` instead.
**Headers:** `content-type: text/event-stream; charset=utf-8`, `cache-control: no-cache`, `x-accel-buffering: no`, `x-request-id`, and `X-Execution-Id` (the run id — reuse it to read the result later at `GET /v1/agents/results/:workflowId/:taskId`, [Agents](/one-api/agents)).
## 4.4 Verified examples
An agent OpenAI-shaped stream — the primer, content deltas, a final `usage` chunk, and `[DONE]`:
```bash
curl -s -N "$API_BASE/v1/agents/one-agent/execute" \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt":"say hi in one word","stream":true,"max_tokens":16}'
```
A workflow event stream — engine frames pass through verbatim as named `event:` lines:
```bash
curl -s -N "$API_BASE/v1/workflows/execute-stream" \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"workflow":"workflow:\n name: doc-stream\n tasks:\n - type: task\n id: t1\n agent: one-agent\n prompt: say hi"}'
```
# OpenRouter compatibility
Source: https://www.plungeai.com/docs/one-api/openrouter-compat
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](/one-api/models#38-get-v1models)) |
```bash
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):
```bash
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](/one-api/authentication)) |
| Auth header | `Authorization: Bearer` | `Authorization: Bearer` | none |
| Model slug | `provider/model` | `provider/model` | none (bare aliases also resolve, [3.7](/one-api/models#37-providers--slugs)) |
| Fallback list | `models: [...]` | `models: [...]` | none — same field, same semantics ([3.3](/one-api/models#33-routing-models-sort-failover)) |
| 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](/one-api/models#35-response-object)) | none |
| Provider routing | `provider:{...}` request object | not interpreted (see below) | use `sort` / `models` / [presets](/one-api/models#34-presets) |
## 5.2 Endpoints
| OpenRouter path | Ocean | Notes |
|---|---|---|
| `POST /chat/completions` | **same** | field caveats in [5.3](#53-request-fields) |
| `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](#56-models-list)) |
| `POST /embeddings` | **differs** | openai, mistral, google and cohere models; no `/embeddings/models` list route ([3.9](/one-api/models#39-embeddings)) |
| `POST /completions` (legacy text) | **same** | `prompt` must be one string; served on `/v1` and `/api/v1` ([3.1](/one-api/models#31-endpoints)) |
| `GET /generation` (cost lookup) | **same** | `GET /generation?id=` ([3.15](/one-api/models#315-account--generation-routes)); traces stay at `GET /v1/traces/:id` ([Traces](/one-api/traces-observability)) |
| `GET /credits`, `/key` | **same** | ([3.15](/one-api/models#315-account--generation-routes)); `/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](/one-api/models#315-account--generation-routes)) |
| `/images`, `/audio/*` | **missing** | not offered |
| `/v1/agents`, `/v1/workflows`, `/v1/tools`, `/v1/mcp`, `/v1/discovery`, `/v1/traces` | **Ocean-only** | the execution planes ([5.9](#59-ocean-only-capabilities-you-gain)) |
## 5.3 Request fields
Full per-provider-shape table is in [3.2](/one-api/models#32-request-fields). 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](/one-api/models#32-request-fields). |
| `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](/one-api/models#35-response-object)), otherwise ignored |
| `usage:{include:true}` | opt-in cost accounting | **supported** — adds `usage.cost`, `cost_details`, `is_byok` ([3.5](/one-api/models#35-response-object)); 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](/one-api/models#33-routing-models-sort-failover)) |
| `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](/one-api/models#35-response-object)); 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](/one-api/streaming)) |
| Keepalive comment | `: OPENROUTER PROCESSING` | **same on `/api/v1`** (until the first byte); none on `/v1`; the [agents plane](/one-api/streaming#43-agents-plane-openai-shaped-stream) 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](/one-api/models#38-get-v1models)). `/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](/one-api/errors-limits).
```bash
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](#50-drop-in-mode)).
## 5.8 What breaks an OpenRouter SDK user today
Every item below describes `/v1`. Under `/api/v1` ([5.0](#50-drop-in-mode)) the error envelope, `gen-` ids, keepalive, usage chunk and `/models` behave as OpenRouter's.
- **`error.code` is a string**, not a number — code that branches on `err.error.code === 401` never matches (compare `err.status` / the HTTP status instead).
- **Cost fields are opt-in** — `usage.cost`, `usage.cost_details`, `usage.is_byok` appear with `usage:{"include":true}` ([3.5](/one-api/models#35-response-object)); without it, read `x-ocean-cost-usd` or `GET /v1/generation` ([3.15](/one-api/models#315-account--generation-routes)).
- **No `openrouter_metadata`** in the response; `provider` and `native_finish_reason` are present.
- **OpenRouter-only request fields are not honoured** — `provider`, `route`, `transforms`, `plugins` are accepted and removed before the provider, with no effect. Replace with `models` + `sort` + [presets](/one-api/models#34-presets).
- **No `: OPENROUTER PROCESSING` keepalive** on the money-plane stream; openai-shape streams carry a final `usage` chunk only if you pass `stream_options.include_usage`.
- **`/models` shape differs** — needs a key, and `pricing` is per-1M numbers (the per-token strings are in `pricing_per_token`).
- **`HTTP-Referer` / `X-Title` are ignored** — no app rankings / attribution.
- **No `/images`, `/audio`, `/responses`, `/messages`, `/providers`** endpoints; `/generation`, `/credits`, `/key` ([3.15](/one-api/models#315-account--generation-routes)) and `/completions` ([3.1](/one-api/models#31-endpoints)) are served.
- **BYOK is dashboard-configured**, not a per-request `provider` object ([3.12](/one-api/models#312-byok-or-billed)).
## 5.9 Ocean-only capabilities you gain
- **Execution planes** an OpenRouter key cannot reach: [Tools](/one-api/tools), [Agents](/one-api/agents), [Workflows](/one-api/workflows), [MCP](/one-api/mcp), [Discovery](/one-api/discovery).
- **`sort`** — route candidates by `price`, `latency`, or `throughput` ([3.3](/one-api/models#33-routing-models-sort-failover)).
- **`@preset/`** — server-stored routing + param bundles ([3.4](/one-api/models#34-presets)).
- **Guardrails** — org/key spend caps, model/provider allow-lists, content filter, `require_byok` ([3.10](/one-api/models#310-guardrails)).
- **Exact-match response cache** — `$0` hits with an `x-cache` header ([3.11](/one-api/models#311-response-cache)).
- **Traces & honest billing** — `GET /v1/traces/:id`, honest served-model reporting after failover ([Traces](/one-api/traces-observability)).
# Tools plane
Source: https://www.plungeai.com/docs/one-api/tools
A **tool agent** is any active registry agent with a typed invocation contract. The tools plane is the shortest path from "I want Brave search" to a result: three routes, one security check, one execution path. Every call becomes a one-task workflow on the engine, so it lands in your execution history and carries an `x-request-id` you can trace ([Traces](/one-api/traces-observability)).
```
Discover ──► GET /v1/tools who is callable (the active catalog)
Inspect ──► GET /v1/tools/:id the contract: operations, params, examples
Execute ──► POST /v1/tools/:id/execute contract check → trust fence → engine → result
```
Auth: `Authorization: Bearer $PLUNGE_API_KEY` (an `ozk_` key — see [Authentication & keys](/one-api/authentication)).
## 6.1 List the catalog — `GET /v1/tools`
Returns the active registry agents as summary cards. Paging: `limit` (default 50, max 100) and `offset`. The response `count` is the size of the page you received, not the catalog total — page until a short page comes back.
```bash
curl -s "https://api.plungeai.com/v1/tools?limit=100" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
Response shape: `{ "tools": [ { "id", "type": "agent", "name", "description", "category", "tags": [...], "status": "active", "credential": {...}, ... } ], "count" }`. `credential` says what the agent runs on: `{ "tier": "platform" }` (our keys — nothing to set up) or `{ "tier": "connection", "provider", "method": "oauth" | "api_key" | "wallet" }` (the caller's own account at that provider — see [Connectors](/one-api/connectors)). Tags worth reading: `gated:` marks operations the trust fence will refuse unattended (see 6.4), `status:active` is the only status this route lists.
On 2026-09-18 the catalog had **92 active tool agents in 14 categories** (`GET /v1/agents/categories` gives the live breakdown; the same query backs `GET /v1/agents`, so the two lists are identical — the difference is how you call them).
## 6.2 Inspect the contract — `GET /v1/tools/:id`
```bash
curl -s "https://api.plungeai.com/v1/tools/brave-agent" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
| Field | Meaning |
|---|---|
| `agent_id`, `name`, `description` | the card identity |
| `operations[]` | `{ name, description?, required_params? }` — the verbs the agent accepts as `operation`; an agent with no operations takes a `prompt` (or its `soleRequiredParam`) |
| `inputSchema` | JSON Schema (`type: object`, `properties`, `required`, `additionalProperties`) for `params`; per-operation requireds live on `operations[].required_params` |
| `examples[]` | `{ title, cnl }` — copy-paste CNL task snippets from the card's demonstrations |
| `output_type` | prose description of what comes back |
| `credential` | `{ tier: "platform" }` or `{ tier: "connection", provider, method }` — the same value the list carries; `GET /v1/tools/stripe-agent` answers `{ "tier": "connection", "provider": "stripe", "method": "api_key" }` |
| `soleRequiredParam` | present when exactly one string param is required — a bare `prompt` is mapped onto it |
| `credentials` | filled by surfaces that know the user's connections; absent here |
A connection-tier agent says so in the same card:
```bash
curl -s "https://api.plungeai.com/v1/tools/stripe-agent" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
Unknown id → `404 unknown_tool` (also in the `X-Error-Code` header):
```bash
curl -s -o /dev/null -w '%{http_code} %header{x-error-code}\n' \
"https://api.plungeai.com/v1/tools/no-such-agent" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
## 6.3 Execute — `POST /v1/tools/:id/execute`
Body (JSON by default; the same fields as YAML with `Content-Type: text/yaml`). The tool's fields may be
sent bare at the root, exactly as the agent's README documents them (`{"expression": "7*6"}`); `params:`
is still accepted and wins on a name clash.
| Field | Type | Notes |
|---|---|---|
| `operation` | string | one of `operations[].name`; omit for prompt-only agents |
| *the tool's fields* | per the contract | bare at the root (`query`, `count`, `expression`, …); validated against the contract |
| `params` | object | the same fields wrapped; merged over the bare ones. Names that collide with task-envelope fields (`agent`, `id`, `type`, …) are `reserved` and rejected either way |
| `prompt` | string | free-text input; satisfies missing requireds on prompt-capable agents |
| `format` | `json` · `yaml` · `markdown` · `text` | response negotiation; `Accept` and the request `Content-Type` also count (default JSON, YAML in → YAML out) |
Order of evaluation, from `orchestration/api-gateway/routes/tools.ts`:
1. **Contract validation.** `reserved` names, or missing requireds on a prompt-less call → `422 invalid_params` with `{ missing, reserved, warnings, contract }` — the contract is echoed so you can fix the call without a second request.
2. **The trust fence** (`checkAgentCall`, mode `unattended`) → `403 refused` or `409 approval_required` — see 6.4. Nothing is executed and no execution row is written.
3. **Execution.** A `workflow_executions` row opens under your user (`execution_id` = the request's trace id), the engine runs the one-task wrapper, the row closes with the result.
Success (`200`):
```json
{ "ok": true, "content": "…markdown or text…", "outcome": "ok", "execution_id": "…", "request_id": "…" }
```
```bash
curl -s -X POST "https://api.plungeai.com/v1/tools/brave-agent/execute" \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H "Content-Type: application/json" \
-d '{"prompt":"cloudflare workers pricing","count":3}'
```
Bare fields, YAML in → YAML out (the Content-Type mirror):
```bash
curl -s -X POST "https://api.plungeai.com/v1/tools/calculator-tool-agent/execute" \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary $'expression: "7*6"\n'
```
Operation form (the weather agent takes `location`; here the contract says what is missing):
```bash
curl -s -w '\n%{http_code}\n' -X POST "https://api.plungeai.com/v1/tools/weather-agent/execute" \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H "Content-Type: application/json" \
-d '{"operation":"current","params":{}}'
```
## 6.4 Outcomes and the trust fence
An agent that cannot do the job answers with a structured **outcome**, not prose (`core/core-base/agent-outcome.ts`). The tools plane maps outcomes onto HTTP (`routes/_util.ts` `outcomeStatus`):
| `outcome` | HTTP | `error.code` | What to do |
|---|---|---|---|
| `ok` | 200 | — | read `content` |
| `needs_input` | 422 | `invalid_params` | fix `params` — `error.detail` and the echoed `contract` say how |
| `needs_connection` | 424 | `connection_required` | the account behind the call has not connected this provider — `error.connect` names the way in, see [Connectors](/one-api/connectors) |
| `needs_api_key` | 424 | `credential_required` | the agent needs a key the account has not stored — `error.connect` names the way in |
| `unavailable` | 503 | `agent_unavailable` | upstream/provider down — retry later or pick another agent |
| `error` | 502 | `execution_failed` | the agent failed; `error.message` carries the agent's own text |
The error body is `{ "error": { "code", "message", "outcome", "detail"?, "content"?, "contract"?, "connect"? } }` plus an `X-Error-Code` header. On a `424`, `connect` is `{ "provider", "method", "link": "POST /v1/connections//link" }`; the Markdown, YAML and text wires print it as a `connect:` line under the execution footer.
**The trust fence** (`core/core-base/call-agent.ts`) runs before every execution and cannot be turned off from the API:
- **Always-gated operations** — `buy`, `purchase`, `fetch_paid`, `send`, `transfer`, `pay`, `send_payment`, `withdraw`, `shop` — plus any `gated:` tag on the card. On this unattended surface they return `403 refused` (a human-in-the-loop surface such as Studio or a harness mission with an explicit allow-list returns `409 approval_required` instead).
- **Guarded categories** — agents in the `payment` and `blockchain` categories are refused unattended unless a mission's `allowed_agents` names them.
- **Not active** — a card that is not `status:active` is `403 agent_not_active`; an unknown id is `404 unknown_tool`.
```bash
curl -s -w '\n%{http_code}\n' -X POST "https://api.plungeai.com/v1/tools/resend/execute" \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H "Content-Type: application/json" \
-d '{"operation":"send","params":{"from":"hello@example.com","to":"nobody@example.com","subject":"x","text":"x"}}'
```
A refusal writes no execution row — a denial is not a run.
## 6.5 The catalog by category (live 2026-09-18)
Ids are exact; `GET /v1/tools/` is the source of truth for operations and for `credential`. **Credential** is that field: `platform` runs on our keys; `connection` needs the caller's own account at the named provider, connected once over the API (chapter 7). A card whose agent also needs an account that is not yet in the machine-readable table (`core/core-base/credential-tiers.ts`) reads `platform`; those are named in the row.
| Category | Agents | Credential |
|---|---|---|
| **search** (14) | `brave-agent`, `exa-agent`, `firecrawl-agent`, `tavily-agent`, `serper-agent`, `serpapi-agent`, `crawl4ai-agent`, `browserbase`, `arxiv-search`, `wikipedia-search`, `newsapi-get-articles`, `nyt-api`, `product-hunt-agent`, `branding-agent` | `platform` |
| **tools** (23) | `calculator-tool-agent`, `markitdown`, `weather-agent`, `wolfram-alpha-full-results`, `data-analyst`, `code-reviewer`, `technical-writer`, `research-analyst`, `research-team`, `learn`, `pdf-worker`, `pptx-worker`, `xlsx-worker`, `ms-office-worker`, `report-worker`, `render-worker`, `cloud-browser-agent`, `gemini-file-search`, `google-docs-export-worker`, `n8n-converter`, `concierge`, `agentic-agent`, `universal-agent` | `platform` (`google-docs-export-worker`: `connection` · `google`) |
| **ai** (3) | `llm-agent`, `vision-agent`, `skill-agent` | `platform` |
| **google-workspace** (25) | `google-gmail`, `google-calendar`, `google-drive`, `google-docs`, `google-sheets`, `google-slides`, `google-forms`, `google-tasks`, `google-contacts`, `google-chat`, `google-meet`, `google-keep`, `google-classroom`, `google-admin`, `google-vault`, `google-analytics`, `google-search-console`, `google-ads`, `google-merchant-center`, `google-business-profile`, `google-books`, `google-youtube`, `google-translate`, `cloud-natural-language`, `google-workspace-assistant` | `connection` · `google` · OAuth (`google-books`, `google-translate`, `cloud-natural-language`: `platform`) |
| **microsoft-365** (12) | `microsoft-outlook-mail`, `microsoft-outlook-calendar`, `microsoft-outlook-contacts`, `microsoft-onedrive`, `microsoft-sharepoint`, `microsoft-teams`, `microsoft-todo`, `microsoft-planner`, `microsoft-onenote`, `microsoft-excel`, `microsoft-word`, `microsoft-powerpoint` | `connection` · `azure` · OAuth |
| **coding-agents** (1) | `gitlab-agent` | `connection` · `gitlab` · OAuth (a personal access token works too) |
| **communication** (2) | `resend`, `cloudflare-email-agent` | `platform` — `send`/`send_batch` gated |
| **media** (2) | `image-gen-agent`, `video-gen-agent` (`generate_video` gated) | `platform` |
| **financials** (1) | `sec-agent` | `platform` |
| **entertainment** (2) | `sports-schedule-agent`, `tv-listings-agent` | `platform` |
| **ecommerce-agents** (2) | `amazon-shopping-agent`, `shopping-zinc` (`buy` gated) | `platform` — `amazon-shopping-agent` also needs an Amazon retailer login, made in Studio |
| **payment** (2) | `x402-wallet-agent` (`fetch_paid` gated), `crossmint-agent` | `x402-wallet-agent`: `connection` · `x402-wallet` · wallet; `crossmint-agent`: `platform` — guarded category |
| **blockchain** (2) | `circle-agent`, `circle-wallet-agent` | `platform` — the Circle wallet is connected in Studio; guarded category |
| **shopping** (1) | `x402-broker` (`execute` gated) | `platform` — needs a wallet connected in Studio |
## 6.6 Using tools from your own LLM loop
The tools plane is designed to sit behind any function-calling model — no SDK required:
1. `GET /v1/tools/:id` for the agents you want; turn each contract into a function definition (`name` = agent id, `parameters` = `inputSchema`, one function per `operations[].name` if you want the model to pick operations).
2. Send the definitions to your model as `tools` (any provider — the [models plane](/one-api/models) works too).
3. When the model returns a tool call, `POST /v1/tools//execute` with `{ "operation", "params" }`.
4. Feed `content` back as the tool result. On `422` feed back `error.message` + `error.detail` — the model fixes its own params. On `403 refused` tell the user a human has to approve that action.
Every hop carries `x-request-id`; keep it in your logs and `GET /v1/traces/:id` shows the run. For a server-side loop that already does all of this (discover → inspect → execute with an approval gate), call the `concierge` agent on the [agents plane](/one-api/agents).
# Connectors & connected accounts
Source: https://www.plungeai.com/docs/one-api/connectors
A **connector** is a third-party integration a user connects **once** — Gmail, Microsoft 365, GitHub, Adobe, Slack, Stripe, Notion, … A **connected account** is that user's live credential row. Tool agents then act *as that user*: `google-gmail` reads the mailbox of whoever owns the `ozk_` key that made the call. Nothing about the credential ever appears in the API.
Three things are easy to confuse:
| Thing | What it is | Where |
|---|---|---|
| **Tool agent** (`google-gmail`) | code that calls a provider's API on behalf of a user | tools / agents planes |
| **Connector** (`google`, `github`, …) | the OAuth app or API-key slot a user connects | Studio → Integrations |
| **Connector proxy provider** (`brave-search`, `firecrawl`, …) | a metered pass-through to a provider's REST API with your own or the platform's key | `api.plungeai.com/v1/connect/…` (7.5) |
## 7.1 Two credential tiers
| Tier | How the user connects | Examples | Stored as |
|---|---|---|---|
| **OAuth** | clicks *Continue with * in Studio, approves at the provider, lands back connected | Google Workspace, Microsoft 365, Adobe, GitHub, GitLab, Slack, Salesforce, HubSpot, Bitbucket, Shopify | encrypted access + refresh token; refreshed server-side when `tokensExpire` |
| **API key / PAT** | pastes a key in the connector dialog | Anthropic, OpenAI, Stripe, Notion (internal token), GitLab PAT, Linear, Jira, Datadog, … | encrypted key; **verified live before storage** (`core/core-secrets/api-key-verify.ts` probes the provider — a rejected key is never saved) |
Both land in `user_credentials` on the compliance database, AES-256-GCM encrypted with per-scope keys; every read is audited (`core/core-secrets/CLAUDE.md`). Agents never receive raw tokens from callers — the platform injects them at run time, which is why `params` names such as `api_key` or `token` are stripped by contract validation.
## 7.2 How a connected account flows into an API call
```
your app ──ozk_ key──► One API ──user_id──► engine ──► google-gmail agent
│ resolveApiKey(google, user)
▼
user_credentials row? yes → call Gmail as the user
no → outcome: needs_connection
```
Without a connection the agent answers with a structured refusal (`success: false`, `outcome: needs_connection`) and the tools plane returns `424 connection_required`:
```json
{ "error": { "code": "connection_required", "message": "Connect your Google account in Studio → Integrations to use google-gmail", "outcome": "needs_connection", "connect": { "provider": "google", "method": "oauth", "link": "POST /v1/connections/google/link" } } }
```
`connect` is the way in: the connector (`provider`), how it connects (`method`: `oauth`, `api_key` or `wallet`) and the route that starts it ([7.3](#73-how-your-testers-connect-an-account)). Every `424` from the tools, agents, workflows and Agent Call routes carries it when the agent is connection-tier; the Markdown and text wires print it as `connect: POST /v1/connections//link` under the execution footer. `credential_required` (`needs_api_key`) carries it the same way.
The same run **with** a connection returns `200 { ok: true, content }` — live example from 2026-09-18 with a Microsoft 365 connection on the calling account (`microsoft-todo`, operation `list_lists`, request id `00000000-0000-4000-8000-000000000003`).
Which agents need what: the `credential` field on every card (`GET /v1/tools/`, the lists, `GET /v1/discovery/search`) — `{ "tier": "platform" }` or `{ "tier": "connection", "provider", "method" }`; the **Credential** column in [the tool catalog](/one-api/tools#65-the-catalog-by-category-live-2026-09-18) is that field. The rule of thumb: `google-*` needs the `google` connector (except the key-based tool APIs `google-books`, `google-translate`, `google-places-agent`, `google-cloud-iot-agent`), `microsoft-*` needs Microsoft 365 (connector id `azure`), `adobe-*` Adobe (not `adobe-pdf-services`), `gitlab-agent` GitLab, `stripe-agent` a Stripe key; search/scrape/document tools run on platform keys.
## 7.3 How your testers connect an account
Connecting is an API call. The `ozk_` key that makes the call owns the connection, and no Studio login is needed:
| | Call | Answer |
|---|---|---|
| **Bring a key** | `POST /v1/connections/:provider` · `{ "api_key", "api_secret"?, "on_behalf_of"? }` | `201 { provider, on_behalf_of, status: "connected" }`; `400 credential_rejected` when the provider refuses the key (providers with a live probe — GitLab today — are checked before anything is stored); `404 unknown_provider` |
| **Send a hosted link** | `POST /v1/connections/:provider/link` · `{ "on_behalf_of"?, "return_url"? }` | `201 { url, expires_at }` |
| **List** | `GET /v1/connections?on_behalf_of=` | `200 { connections: [ { provider, on_behalf_of, method, status, connected_at } ] }` — status only; no token, key or hint is ever returned |
| **Disconnect** | `DELETE /v1/connections/:provider?on_behalf_of=` | `204`; `404 connection_not_found` |
The hosted link is `https://studio.plungeai.com/connect/`: signed, **single-use**, valid **15 minutes**, and it works without a Studio session — send it to the person who owns the account. The page opens with who is asking — your Studio name, else your email — and says that your agents will act on the account on their behalf until they disconnect it. For an OAuth provider the page is *Continue with * → approve at the provider → back to your `return_url` (an `https` URL you gave, with `?connected=` added) or, without one, a confirmation page. Where Studio has no OAuth app for the provider the page is a key form: the pasted key is checked and stored the same way as `POST /v1/connections/:provider`. A tampered or expired link is `404`, a used one `410`.
```bash
curl -s -X POST "https://api.plungeai.com/v1/connections/google/link" \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H "Content-Type: application/json" \
-d '{"return_url":"https://example.com/done"}'
```
**Your customers (`on_behalf_of`).** Name a customer with any id you own — 1 to 128 characters of `A-Z a-z 0-9 _ . -` (no `..`, no `:`) — and the connection is that customer's, not yours: it is stored under your key and your customer id, so two developers who use the same id never collide, `GET /v1/connections` shows only the account you ask for (yours, or the one `on_behalf_of` names) and nobody can reach a customer of another key. An empty `on_behalf_of` is refused, never read as your own account.
To run an agent as a customer, send the same id on the call: `POST /v1/execute` with `on_behalf_of: cust_42` next to `agent:`. A connection-tier agent then uses the account connected under that id (and answers `424` with `connect` when that customer has none) — never yours; the run, its billing and a saved agent stay yours, and the result is returned to you as usual. It is a wire key, like `format`: it never reaches the agent as a field. It is accepted on `POST /v1/execute` only: the per-id doors (`/v1/tools/{id}/execute`, `/v1/agents/{id}/execute`, `/v1/workflows/{id}/execute` and their stream variants) answer `400 invalid_body` rather than quietly running as you.
Provider ids are the connector ids of [7.4](#74-the-connector-catalog) — the `credential.provider` of a card or the `connect.provider` of a `424` names the one to use (`google`, `azure` for Microsoft 365, `github`, `slack`, `stripe`, …).
The same connection can be made in **Ocean Studio** (`https://studio.plungeai.com`), where a signed-in user connects their own account in a browser:
1. Studio → **Integrations** → **Browse connectors** → pick the provider.
2. OAuth connector: **Continue with ** → approve → back in Studio as *Connected*. API-key connector: paste the key → it is probed live → *Connected* (or an inline "rejected" error).
3. Verify with the API: run the agent with the same user's `ozk_` key — the `needs_connection` outcome disappears.
The Studio endpoints behind those buttons (session cookie, not API key — listed so you know what happens, not for you to call): `GET /api/connections` (yours), `GET /api/connections/providers` (public: every OAuth provider and its scopes), `GET /api/auth/oauth/:provider/start`, `POST /api/connections/api-key` (`400` when the live probe rejects the key), `DELETE /api/connections/:provider`. Special flows exist for the Circle agent wallet (`/api/connections/circle-agent-wallet/init|verify`), retailer logins such as Amazon (`/api/connections/retailer/connect`) and QR/browser logins such as WhatsApp (`/api/connections/browser-session/*`) — those connect in Studio only.
```bash
curl -s "https://studio.plungeai.com/api/connections/providers"
```
Redirect URIs and OAuth app registration are platform-side — testers never configure anything at the provider.
## 7.4 The connector catalog
The registry lists **802 connector cards** (`GET /v1/discovery/search?kind=connectors&limit=100`, page with `offset` until `total`; live 2026-09-29). Every card carries `connector.auth` — never null:
| `connector.auth` | Meaning | Count |
|---|---|---|
| `oauth_static` | a platform-registered OAuth app; user clicks Continue | 46 |
| `api_key` | user pastes a key/PAT | 83 |
| `oauth_dcr` | an MCP server the user connects with Dynamic Client Registration (see [MCP plane](/one-api/mcp)) | 519 |
| `none` | platform-key or public provider — nothing to connect. Also what a card reads when its source declared no auth (57 of the 154 today: 35 community mirrors and 22 legacy tool cards) | 154 |
```bash
curl -s "https://api.plungeai.com/v1/discovery/search?kind=connectors&limit=3" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
```bash
curl -s "https://api.plungeai.com/v1/discovery/cards/connector/slack" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
**OAuth apps registered on the platform today** (client id wired in the Studio worker, `wrangler-templates/template.wrangler.ocean.jsonc` ↔ `core/core-secrets/oauth-providers.ts`): `google` (Workspace: Drive, Gmail, Calendar, Contacts, Tasks, Docs, Sheets, Slides, Analytics, Search Console, Merchant Center, Business Profile, YouTube; `google-youtube` also connects YouTube on its own), `azure` (Microsoft 365: OneDrive, Outlook, Calendar, Teams, SharePoint, To Do, Planner, OneNote, Office files), `adobe` (Creative Cloud, Express, Photoshop, Lightroom, Stock, Experience Platform, Journey Optimizer), `github`, `gitlab`, `bitbucket`, `slack`, `salesforce`, `hubspot`, `shopify`. Provider entries also exist for `sonos`, `spotify`, `tesla`, `facebook`, `linkedin`, `twitter`, `reddit`, `tiktok`, `pinterest`, `snapchat`, `intuit`, `square`, `zoom`, `sentry`, `notion` — their cards show in Studio, but the OAuth app is not registered yet, so *Continue with …* bounces with `oauth_not_configured`; connect those with an API key where the card offers one.
By category (ids as the registry spells them):
| Group | Connectors |
|---|---|
| **AI & ML** | `openai`, `anthropic`, `huggingface`, `ibm` |
| **Google** | `google` (Workspace umbrella), `google-drive`, `google-gmail`, `google-calendar`, `google-contacts`, `google-tasks`, `google-youtube` |
| **Microsoft** | `azure` (Microsoft 365 umbrella), `microsoft-onedrive`, `microsoft-outlook-mail`, `microsoft-outlook-calendar`, `microsoft-teams`, `microsoft-sharepoint`, `microsoft-365` |
| **Adobe** | `adobe`, `adobe-express`, `adobe-photoshop`, `adobe-lightroom`, `adobe-stock`, `adobe-cc-files`, `adobe-aep`, `adobe-ajo`, `adobe-workfront` |
| **Developer & cloud** | `github`, `gitlab`, `bitbucket`, `postman`, `aws`, `cloudflare`, `vercel`, `netlify`, `supabase`, `databricks`, `snowflake` |
| **Observability** | `sentry`, `datadog`, `grafana-mcp-server`, `honeycomb`, `incident-io`, `logrocket-mcp`, `jam` |
| **Communication** | `slack`, `discord`, `telegram`, `twilio`, `sendgrid`, `mailchimp`, `resend`, `whatsapp`, `whatsapp-personal` |
| **Productivity & PM** | `notion`, `airtable`, `asana`, `jira`, `linear`, `monday`, `clickup`, `trello`, `basecamp`, `teamwork`, `craft`, `mem`, `roam-research`, `ticktick`, … (28 productivity + 19 project-management cards) |
| **CRM, sales & marketing** | `salesforce`, `hubspot`, `pipedrive`, `zoho`, `close`, `helpscout`, `intercom`, `attio`, `apollo-io`, `klaviyo`, `lemlist`, `outreach`, `salesloft`, `zoominfo`, … (49 marketing-and-sales cards) |
| **Payments & finance** | `stripe`, `paypal`, `square`, `plaid`, `xero`, `circle`, `circle-agent-wallet`, `x402-wallet`, `intuit`, `intuit-quickbooks`, `intuit-payments`, `intuit-payroll`, `intuit-time-tracking`, `airwallex` |
| **Data & analytics** | `amplitude`, `mixpanel`, `segment`, `looker`, `alpha-vantage` |
| **Search & scraping (platform-key providers)** | `brave-search`, `exa`, `firecrawl`, `crawl4ai`, `browserbase`, `scrapingbee`, `serpapi`, `serper`, `perplexity-search`, `news-api`, `nyt`, `arxiv`, `wikipedia`, `product-hunt`, `weather`, `wolfram-alpha` |
| **Social** | `facebook`, `instagram`, `threads`, `linkedin`, `twitter`, `reddit`, `pinterest`, `snapchat`, `tiktok`, `youtube`, `bereal`, `clubhouse` |
| **Commerce & travel** | `shopify`, `amazon`, `target-app`, `shop`, `instacart`, `expedia`, `booking`, `tripadvisor`, `uber`, `viator`, `kiwi-com`, `amadeus`, … |
| **Legal, security, research, lifestyle** | 13 legal, 15 security, 8 research and 11 lifestyle MCP-server connectors (`oauth_dcr` / `none`) |
## 7.5 The connector proxy — `/v1/connect/:provider/proxy/*`
For providers you would otherwise call directly, the inference gateway offers a **metered pass-through** (`inference/gateway/src/routes/connector-proxy.ts`): one `ozk_` key, one bill, transparent bytes in both directions (streaming included, no body transformation).
| | |
|---|---|
| **Key** | your `ozk_…` platform key (Dashboard → One API → Keys, `https://dashboard.plungeai.com`); a legacy `sk-conn-…` key is still accepted until it is revoked and replaced |
| **Route** | `ANY https://api.plungeai.com/v1/connect//proxy/` — the path after `/proxy/` is appended to the provider's `base_url` |
| **Mode** (from the `connector_providers` rate card) | `byok` — your own upstream token connected for the org is used, you pay `byok_routing_fee`; `platform` — the platform's key is used, you pay `price_per_call`; `both` — your token if connected, else the platform's |
| **No connection, provider is BYOK-only** | `402` "connection required" |
| **Unknown or inactive provider** | `404 model_not_found` / `400 provider_unavailable` |
| **Limits** | per-provider `rate_limit_rpm`; org spend caps and guardrails apply exactly as on the models plane |
| **Auth failures** | `401 invalid_api_key`; a suspended or closed account is `401 Account suspended` |
Example (one metered provider call, so it carries no `# verify` marker):
```bash
curl "https://api.plungeai.com/v1/connect/brave-search/proxy/res/v1/web/search?q=cloudflare+workers" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
Each call is metered as a `connector_requests` row and appears in the Dashboard next to your inference spend. Prefer the tools plane when you want the agent's judgement (result formatting, pagination, outcome vocabulary); use the proxy when you want the provider's raw API with one key and one bill.
# Discovery
Source: https://www.plungeai.com/docs/one-api/discovery
Discovery is the read-only catalog behind every other plane: agents, skills, plugins, MCP servers, connectors, models, providers and (once you have saved some) workflows. Four routes, all `ozk_`-authenticated, all backed by the registry service (`orchestration/api-gateway/routes/discovery.ts` → `orchestration/registry/`).
## 8.1 Search — `GET /v1/discovery/search`
| Param | Values | Notes |
|---|---|---|
| `q` | text | natural-language or keyword query; with `q` the registry runs **hybrid** search (semantic + keyword), without it a plain listing |
| `kind` | `agents` · `skills` · `plugins` · `mcp-servers` · `connectors` · `models` · `providers` · `workflows` · `experts` · `backgrounds` · `personas` (= `digital-twins`) | the plural catalog groups (`registry.ts` `KIND_GROUPS`) |
| `type` | singular storage type (`agent`, `skill`, `mcp_server`, …) | alternative to `kind` |
| `category` | e.g. `search`, `google-workspace` | exact card category |
| `status` | `active` (default) | the public catalog shows callable capabilities only; other statuses need an explicit value |
| `tier` | card tier tag | e.g. `verified` |
| `mode` | `hybrid` (default with `q`) · `keyword` · `vector` | force one search leg |
| `fields` | `summary` · `full` | `full` adds parameters, demonstrations and `prompt_format` |
| `limit`, `offset` | ints | `limit` is capped by the registry; page with `offset` |
| `include` | `quality` | attaches a `quality` object per agent card — `{ score, success_rate, runs }` or `null` when the evaluation service has no data (it was `null` for every card on 2026-09-18) |
Response: `{ "cards": [...], "count": , "total": , "searchMethod": "text" | "hybrid" | "vector", "query": { …the normalised query… } }`. `count` is the page size; `total` is every card the filters (and, for `q`, the keyword predicate) match, ignoring `limit` and `offset` — page with `offset` until you have seen `total`. For a `q` search the vector leg can add cards the keyword predicate did not match, so `total` is a floor, never smaller than `offset + count`.
```bash
curl -s "https://api.plungeai.com/v1/discovery/search?q=web+search&kind=agents&limit=3" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
```bash
curl -s "https://api.plungeai.com/v1/discovery/search?q=llm&kind=agents&limit=2&include=quality" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
Catalog sizes on 2026-09-18 (`limit=100`, first page): agents **92**, skills **12**, plugins **5**, providers **11**, mcp-servers **100+**, models **100+**, connectors **802** as of 2026-09-29 (9 pages of 100 — read `total`), workflows 0 for a fresh key (saved workflows are per user), experts / backgrounds / personas 0 (capability cards are created by users and missions — see [Skills, plugins & capability injection](/one-api/skills-plugins-capabilities)).
## 8.2 The card object
Summary cards (`fields=summary`, the default for the agents/tools lists) carry: `id`, `type`, `name`, `version`, `description`, `category`, `best_for[]`, `not_for[]`, `tags[]`, `status`, `icon`, `color`, `created_at`, `updated_at`. Full cards add `parameters[]`, `demonstrations[]`, `prompt_format`, `output_type`, `output`. Connector cards add a `connector` object (`auth`, `provider_key`, `key_placeholder`, `help_text`); model cards add pricing and capability columns from the rate card.
Tags encode the operational facts the planes act on:
| Tag | Meaning |
|---|---|
| `status:active` | callable — the only status the tools/agents planes accept |
| `gated:` | the trust fence refuses `` unattended (403) — [Tools plane](/one-api/tools) |
| `tier:verified` | reviewed card |
| `auth:oauth_static` · `auth:api_key` · `auth:oauth_dcr` | how a connector is connected — [Connectors](/one-api/connectors) |
| `serves:agent` | a connector some tool agent uses |
## 8.3 Recommend — `POST /v1/discovery/recommend`
Body `{ "type": "agent" | "skill" | … (plural kinds accepted as aliases), "task": "", "status"?: "" }`. **Since 2026-09-25 recommend rides on the Jev resolve ([§8.4](#84-resolve--post-v1discoveryresolve)) and works again** — from 2026-09-18 it answered `502 upstream_error` for every request. `status` defaults to `active`. Response: `{ "recommended": "" | null, "card": { …the card… } | null, "score": <0–1>, "resolve": { …the full resolve decision… } }` — `recommended` is `null` and `score` is `0` when nothing fits; `score` is also `0` on the keyword fallback (no confidence). Only the types in the discovery index can be recommended — `agent`, `skill`, `connector`, `mcp_server`, `workflow`, `plugin`, `expert`, `digital_twin`; any other valid type (`model`, `provider`, `background`, `component`, `theme`, …) answers `400 type_not_discoverable` (legacy aliases such as `ai_agent` are normalised first), an unknown type `400 invalid_request`.
## 8.4 Resolve — `POST /v1/discovery/resolve`
One step from a task to a call: which tool, which operation, what command, and how sure the platform is. The registry shortlists cards per kind (BM25 over id, name, description, `best_for` and tags), one decision request to the Jev model (`typesafe-provider`) picks a card per kind or none, and the answer carries that card's operation, its required parameters, an example CNL task and a calibrated confidence (`orchestration/registry/resolve.ts`).
```bash
curl -s -X POST "https://api.plungeai.com/v1/discovery/resolve" \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"task": "current weather conditions for Berlin", "kinds": ["agents"]}'
```
The body is whitelisted — any other field is dropped; you never pass an owner or a debug flag, so only public cards are considered.
| Field | Values | Notes |
|---|---|---|
| `task` | text, required | what you want done, in plain language; missing or empty → `400 invalid_json` |
| `kinds` | array of card types: `agent` · `workflow` · `connector` · `mcp_server` · `skill` · `plugin` · … | plural kinds (`agents`, `mcp-servers`, …) are accepted aliases; deduplicated, at most 8; default: `agent`, `workflow`, `connector`, `mcp_server`, `skill`, `plugin` |
| `category` | e.g. `search` | exact card category |
| `status` | `active` (default) · `ready` · `any` · a literal status | same default as search: callable capabilities only |
Response fields:
| Field | Meaning |
|---|---|
| `outcome` | `match` · `uncertain` · `none` — see below |
| `tool` | the chosen card: `{ id, kind, type, name, description, category, status }`; absent on `none` |
| `operation` | `{ name, purpose?, gated }` — absent when the card has several operations and none was decided; `gated: true` means the [trust fence](/one-api/tools) needs an approval |
| `call` | `{ required_params[], example_cnl? }` — the example is a CNL task you can adapt ([workflows](/one-api/workflows)) |
| `confidence` | 0–1, calibrated; `null` on the keyword fallback |
| `alternatives[]` | up to 5 runners-up `{ id, kind, name, p }` |
| `gate` | advisory signals from the same request: `acts_on_user_system`, `needs_external_service`, `prose_suffices` (0–1) and a suggested `handler` |
| `method` | `all` (every card of each kind was shown to the model) · `bm25` (a keyword shortlist) · `cache` (a stored decision) · `lexical-fallback` (the decision model was unavailable) · `rate-limited` (a public caller exceeded the per-isolate Jev budget — the pick is a keyword match with `confidence: null`) |
| `pass` | `1`, or `2` when a second, closer comparison of the top three was needed |
| `mode` | `shadow` · `act` — how the platform's own agents use resolve (in `shadow` they log the decision and keep showing the plain search list); it does not change your answer |
| `index_version`, `timings` | the catalog snapshot the decision used, and the server-side legs in ms |
What each outcome means for a caller:
- **`match`** (confidence ≥ 0.8) — call `tool.id` with `operation.name`; fill `call.required_params` (the card at [§8.5](#85-one-card--get-v1discoverycardstypeid) has every parameter). Ask your user first when `operation.gated` is `true`.
- **`uncertain`** — the best guess is below 0.8, or `method` is `lexical-fallback` or `rate-limited` and the pick is a keyword match (`confidence: null`). Treat `tool` and the first `alternatives` as a shortlist: read their cards and choose, or ask your user.
- **`none`** — no card in the catalog fits this task, or the task needs no tool at all (plain text answers it). Do not invent an id; answer without a tool or tell your user nothing fits.
Decisions for the same task, kinds, status and category are cached (10 minutes in memory, 6 hours in KV) until the catalog snapshot changes.
## 8.5 One card — `GET /v1/discovery/cards/:type/:id`
Returns the **full card as markdown with YAML frontmatter** (`Content-Type: text/markdown`) — the same document the platform's own agents read before calling a tool. `:type` is the singular storage type (`agent`, `skill`, `plugin`, `mcp_server`, `connector`, `model`, `provider`, `workflow`, `expert`, `background`, `digital_twin`); the plural `kind` spellings (`agents`, `mcp-servers`, …) are accepted as aliases.
```bash
curl -s "https://api.plungeai.com/v1/discovery/cards/agent/brave-agent" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
Unknown card → `404 not_found`. A 4xx from the registry is normalised into the One API error envelope; a registry 5xx never reaches you raw (`502 upstream_error`).
## 8.6 From an id to a call
| Card `type` | Use it as |
|---|---|
| `agent` | `GET /v1/tools/` + `POST /v1/tools//execute` ([tools](/one-api/tools)); `POST /v1/agents//execute` ([agents](/one-api/agents)); `agent: ` in a CNL task ([workflows](/one-api/workflows)) |
| `model` | `"model": ""` on `POST /v1/chat/completions` — ids are `provider/model` slugs ([models](/one-api/models)); also `model:` + `provider:` on an agent-plane call |
| `provider` | the `provider/` prefix of model slugs; `provider:` on agent-plane calls |
| `skill` · `plugin` · `expert` · `background` · `digital_twin` | `skills: []`, `plugins: []`, `experts: []`, `backgrounds: []`, `persona: ` on a `type: harness` task ([capability injection](/one-api/skills-plugins-capabilities)) |
| `mcp_server` | `POST /v1/mcp/runs` with the server id ([MCP plane](/one-api/mcp)); `mcp: []` on a harness mission |
| `connector` | connect it in Studio → Integrations; then the agents that `serves:agent` it stop returning `needs_connection` ([connectors](/one-api/connectors)) |
| `workflow` | `POST /v1/workflows//execute` ([workflows](/one-api/workflows)) |
Search results are cached briefly on the platform side (KV + LRU, ~200 ms per uncached call); agents that discover at run time — `concierge`, the harness loop — hit this same route, so what you see is what they see.
# Agents plane
Source: https://www.plungeai.com/docs/one-api/agents
The agents plane runs one registry agent per request and hands back its result. It is the
execution plane you reach with an `ozk_` key (see [Authentication](/one-api/authentication)) at
`POST /v1/agents/:id/execute`. Every call wraps exactly **one** minimal CNL task in the engine
envelope, so the agents plane is the single-task front door; multi-task orchestration lives on the
[Workflows plane](/one-api/workflows) and bounded autonomous missions on a `type: harness` task
there (see [capability injection](/one-api/skills-plugins-capabilities)).
Base URL is always `https://api.plungeai.com`. All examples below use `$PLUNGE_API_KEY` for an
`ozk_…` key. Source: `orchestration/api-gateway/routes/agents.ts`.
## 9.1 Agent tiers
An **agent** is a registry card whose id equals its Worker service name. There are ~90 active
agents (`GET /v1/agents?limit=100` returned `count: 92`, live-verified 2026-09-19; the default page is 50 — follow `has_more`/`next_offset`). They fall into these
tiers. Every id below is **status:active** in the registry unless the row says otherwise
(`agents/agents/README.md`, `agents/agents/CORE-AGENTS-DECISION.md`).
| Tier | id(s) | What it is | Use when | Call how |
|---|---|---|---|---|
| **Tool agent** | ~90 ids: `serper-agent`, `exa-agent`, `tavily-agent`, `sec-agent`, `serpapi-agent`, `markitdown`, `weather-agent`, … | a registry agent with a typed invocation contract (parameters/operations) | you need one bounded capability (search, documents, finance, a connector) | `POST /v1/agents/:id/execute` with a `prompt`; or the [Tools plane](/one-api/tools) `POST /v1/tools/:id/execute` with typed params; or an `agent:` task in a workflow |
| **`one-agent`** — the agent runtime | `one-agent` | the one runtime, two modes chosen by the payload. **Call mode** (no loop command): a single LLM call — YAML in → provider RPC → result — with `persona` / `expert(s)` / `skill(s)` injection and `messages` threading. **Loop mode** (`max_iterations` / `maxTurns` or a mission): a bounded ReAct mission — tool fence, iteration cap, long-term memory, capability injection | any generation (pinned model/provider, shaped by a persona or skill) and multi-step autonomous work that calls tools/sub-agents | call mode: agents-plane execute; `model`/`provider`/`system`/`messages`/`persona`. Loop mode: a `type: harness` task with `agent: one-agent` on the [Workflows plane](/one-api/workflows) — the agents plane forwards no mission fields (see 9.3), so it runs call mode only |
| **`concierge`** | `concierge` | interactive Discover→Inspect→Execute front-door over any agent, with a payment/approval gate | "do X" where the platform must find and call the right agent, with a money gate | its own approval contract (9.7) and the [MCP plane](/one-api/mcp) `plungeai_continue`; on the raw agents plane re-send with `approved: true` + `action_token`, or approve by prompt (`approve `, 9.7) |
| **`claude-managed-agent`** | deployed, **not** in the active list | Anthropic hosted Managed Agents runtime for autonomous cloud code execution | autonomous cloud code runs (higher infra overhead) | not exposed on the ozk agents plane today — live-verified 2026-09-18 it is absent from `GET /v1/agents`, so `/v1/agents/claude-managed-agent/execute` returns `404 unknown_agent` |
| **Campaign kind** | Studio `kind: campaign` (no `campaign-agent` Worker) | a scheduled, ledger-driven workflow that works a task list in short runs | recurring bounded batch work | via the scheduler / MCP `plungeai_schedule` / Studio, not a callable agent id (`agents/agents/campaign-agent/README.md`) |
> **Legacy ids.** `llm-agent`, `skill-agent`, `universal-agent` and `agentic-agent` are frozen
> legacy ids: they stay callable until the switch-over, then re-point to `one-agent`. Write
> `agent: one-agent` everywhere — a `type: harness` task with **no** `agent:` line still runs on the
> legacy `harness-agent` loop today. The bare `harness-agent` binding is not a callable registry id
> on the planes (live-verified: `404 unknown_agent`).
## 9.2 List agents & categories
- `GET /v1/agents?limit=<1..100>&offset=` → `{ agents: [...], count, limit, offset, has_more, next_offset }` — `count` is the page size (default limit 50); when `has_more` is true, fetch the next page at `offset=next_offset`. Each card carries `id`,
a summary and `tags` (including `status:active`).
- `GET /v1/agents/categories` → `{ categories: [{name, count}], count }`, aggregated from the
active-agent summaries (there is no separate categories store).
```bash
curl -s "https://api.plungeai.com/v1/agents?limit=5" \
-H "Authorization: Bearer $PLUNGE_API_KEY"
```
## 9.3 Execute — body reference
`POST /v1/agents/:id/execute`. JSON body (default) or an Ocean-format `text/yaml` body with the
same fields. Every accepted field, from `routes/agents.ts`:
| Field | Type | Status | Meaning |
|---|---|---|---|
| `prompt` | string | **supported** — required unless `messages` has a user turn | the per-run input. With no `prompt`/`input`, the last non-blank user message of `messages` is used; neither → `400 missing_prompt` |
| `input` | string | supported | alias for `prompt` |
| `sync` | boolean | supported (default `true`) | `false` → `202` pointer, poll the result route (9.5) |
| `stream` | boolean | supported (default `false`) | `true` → an OpenAI-shaped SSE stream (9.6) |
| `messages` | array | supported | prior chat turns `[{role, content}]`; **`prompt` is optional when `messages` has a user turn** (the last one is used). `one-agent` (call mode) sends the messages — not a separate prompt — to the model, so it answers from the whole conversation; any other agent receives `messages` as an input field and threads it only if it reads it. For a conversation that survives across calls use the [MCP plane](/one-api/mcp) (`session_id`, `plungeai_continue`) |
| `system` | string | supported | system-prompt frame forwarded to the agent |
| `model` | string | supported | pin a model; validated against the catalog → `422 unknown_model` if unknown/inactive |
| `provider` | string | supported | pin a provider; normalised (`google`→`gemini`). Must travel **with** `model` — a pinned model without its provider routes to the agent default |
| `temperature` | number | supported | sampling temperature |
| `top_p` | number | supported | nucleus sampling |
| `max_tokens` / `maxTokens` | number | supported | output-token budget (must be `> 0`) |
| `reasoning_effort` | string | supported | reasoning-effort hint forwarded to the model |
| `thinking_level` | string | supported | thinking-level hint forwarded to the model |
| `persona` | string | supported | the **only** capability field forwarded on this plane |
| `format` | `json`\|`yaml`\|`markdown`\|`text` | supported | response format (also `?format=` or `Accept`). Unknown value → `400 invalid_format` (`lib/format.ts`) |
| `user_context` | object · optional · default: derived from your request's network location (time zone, approximate place) | supported | the caller's time zone, locale and location (`{time_zone, locale, place, latitude, longitude, accuracy_m, source}`, the `UserContext` schema); the agent uses it for local time and as the default schedule time zone |
Fields not in this table (e.g. `goal`, `mission`, `allowed_tools`, `skills`, `experts`, `mcp`)
are **ignored** on the agents plane — run those through a `type: harness` task on the
[Workflows plane](/one-api/workflows). Two more fields are accepted only as a pair, the approve
round-trip of [9.7](#97-the-concierge-approval-flow): `approved: true` with `action_token` (a string
of 1-128 characters), plus an optional `allow_scope` (`once` | `run` | `always`, anything else is
`once`). One without the other, or an `approved` that is not exactly `true` or `false`, is
`400 invalid_params`; `approved: false` alone is a fresh turn.
### Conversation without a prompt
An OpenAI-style body is a valid agent call — the model answers from the whole conversation:
```bash
curl -s "https://api.plungeai.com/v1/agents/one-agent/execute" \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"messages":[{"role":"user","content":"My name is Ada."},{"role":"assistant","content":"Hello Ada."},{"role":"user","content":"Reply with only my name."}],"max_tokens":16}'
```
### Sync execute (default)
```bash
curl -s "https://api.plungeai.com/v1/agents/one-agent/execute" \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"prompt":"Reply with only the word: pong","max_tokens":24}'
```
Success (`200`): `{ "content": "...", "workflow_id": "...", "task_id": "t1", "request_id": "..." }`.
`request_id` equals the trace id (see [Traces](/one-api/traces-observability)).
### Model / provider pinning
Pin a model and provider together; discover models with `GET /v1/models`
([Models plane](/one-api/models)) or `GET /v1/discovery/search?kind=models` ([Discovery](/one-api/discovery)).
```bash
curl -s "https://api.plungeai.com/v1/agents/one-agent/execute" \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"prompt":"Reply with only: pong","provider":"openai","model":"gpt-4o-mini","max_tokens":16}'
```
## 9.4 Async execution (202 + poll)
Send `"sync": false`. The response is `202` with a pointer `{ workflow_id, task_id, request_id }`;
read the result from the result route once it is ready.
```bash
RESP=$(curl -s "https://api.plungeai.com/v1/agents/one-agent/execute" \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"prompt":"Reply with only the word: pong","max_tokens":24,"sync":false}')
echo "$RESP"
WF=$(printf '%s' "$RESP" | sed -n 's/.*"workflow_id":"\([^"]*\)".*/\1/p')
TASK=$(printf '%s' "$RESP" | sed -n 's/.*"task_id":"\([^"]*\)".*/\1/p')
for i in 1 2 3 4 5 6 7 8; do
R=$(curl -s "https://api.plungeai.com/v1/agents/results/$WF/$TASK" \
-H "Authorization: Bearer $PLUNGE_API_KEY")
case "$R" in *'"content"'*) echo "$R"; break;; *) sleep 2;; esac
done
```
## 9.5 Results route
`GET /v1/agents/results/:workflowId/:taskId` → `{ content, content_type, workflow_id, task_id }`.
It reconciles the run's terminal state (`lib/execution-row.ts`):
| Situation | Status | Code |
|---|---|---|
| result stored | `200` | — |
| run still in flight | `404` | `not_ready` |
| run failed | `409` | `execution_failed` |
| run completed but produced no visible content | `422` | `empty_completion` (raise `max_tokens ≥ 64`) |
## 9.6 Streaming
Send `"stream": true` on `POST /v1/agents/:id/execute`. The agents plane emits an
**OpenAI-shaped** chunk stream (the only stream shape on this plane) — `Content-Type:
text/event-stream`, `data:` lines only (no named `event:` lines), each a `chat.completion.chunk`,
terminated by `data: [DONE]`. This mirrors the models plane; see [Streaming](/one-api/streaming).
Relay + encoder: `lib/stream-relay.ts` (`relayEngineStream`, `openaiEncoder`).
Exact request shape:
```bash
curl -sN "https://api.plungeai.com/v1/agents/one-agent/execute" \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"prompt":"Reply with only the word: pong","max_tokens":24,"stream":true}'
```
Observed wire (live-verified 2026-09-18):
```text
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":…,"model":"gemini-3.1-flash-lite","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"pong"},"finish_reason":null}]}
data: {"id":"chatcmpl-…","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":72,"completion_tokens":1,"total_tokens":73}}
data: [DONE]
```
Notes: `model` is the pinned model, else the provider's default (never the agent id); a final
`usage` is included when the provider reported the token split. On an error **after** the first
byte the stream emits `data: {"error":{…}}` and closes **without** `[DONE]` (OpenRouter behaviour);
a pre-dispatch rejection is a JSON error (see 9.8), not a stream.
## 9.7 The concierge approval flow
`concierge` runs a bounded Discover→Inspect→Execute loop with three tools — `find_agent`
(discover), `agent_card` (inspect), `run_agent` (execute) — and a payment gate
(`agents/agents/concierge-agent/README.md`). Money operations (`buy`, `purchase`, `send`,
`transfer`, `pay`, `withdraw`, `shop`, …) and any card `gated:` are held for approval:
1. A gated action returns `{ "needs_approval": true, "pending_action": {…}, "action_token": "<10-char>", "messages": [...] }`.
2. Re-invoke with the same `messages`, plus `"approved": true` and the `action_token`, to execute
and continue. The token is held server-side for 1 hour. `ask_user` questions work the same way
(append the user's answer to `messages`).
On the One API this handshake is surfaced through the [MCP plane](/one-api/mcp): the pause arrives
as a ⏸ `continuation`, and you approve with `plungeai_continue { approve: true }` (never approve on
the user's behalf). The **raw agents plane takes the same pair** (`routes/agents.ts`): send the next
call with the same `messages`, `"approved": true` and the `action_token` (1-128 characters; a half
pair is `400 invalid_params`) and the agent checks the token against your key's pending actions. You
can also approve by prompt: a paused reply ends with
`Running outside the chat? Re-run with input: approve `, so send `{"prompt":"approve "}`
as the next call and the held action runs for your key. An unknown, used or expired token is a
graceful no-op that runs no model and spends nothing — the example uses one:
```bash
curl -s "https://api.plungeai.com/v1/agents/concierge/execute" \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"prompt":"approve 0000000000","max_tokens":64}'
```
(A real approval run can spend money, so it is not run here.)
## 9.8 Errors
Pre-dispatch and post-dispatch errors from `routes/agents.ts` / `routes/_util.ts`. Full envelope
and header reference: [Errors, limits & headers](/one-api/errors-limits).
| Status | Code | When |
|---|---|---|
| `400` | `missing_prompt` | no `prompt`/`input` string |
| `400` | `invalid_format` | `format` is not `json`/`yaml`/`markdown`/`text` |
| `404` | `unknown_agent` | no such agent id (fenced before dispatch) |
| `403` | `agent_not_active` | the card is not `status:active` |
| `422` | `unknown_model` | pinned `model` absent/inactive in the catalog |
| `422` | `empty_completion` | run completed but produced no visible content |
| `422` | `invalid_params` | agent outcome `needs_input` |
| `424` | `connection_required` | outcome `needs_connection` (connect an account — chapter 7) |
| `424` | `credential_required` | outcome `needs_api_key` |
| `503` | `provider_unconfigured` | the provider has no API key configured on the platform |
| `503` | `agent_unavailable` | outcome `unavailable` |
| `502` | `engine_error` / `execution_failed` | the engine/agent failed |
| `502` | `result_unavailable` | run finished but the result could not be retrieved |
| `409` | `duplicate_execution_id` | a replayed `x-trace-id` UUID |
| `413` | `payload_too_large` | body over the cap (default 1 MiB) |
```bash
curl -s "https://api.plungeai.com/v1/agents/no-such-agent-xyz/execute" \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: application/json' \
-d '{"prompt":"hi","max_tokens":8}'
```
# Workflows plane
Source: https://www.plungeai.com/docs/one-api/workflows
The workflows plane runs a **CNL** workflow — one or many agent tasks orchestrated by the engine —
and hands back a pointer to the result. Reach it with an `ozk_` key
([Authentication](/one-api/authentication)) at `POST /v1/workflows/execute` (inline) or
`POST /v1/workflows/:id/execute` (saved). Base URL is always `https://api.plungeai.com`.
Source: `orchestration/api-gateway/routes/workflows.ts`, engine `orchestration/cnl-engine/`.
Where the [Agents plane](/one-api/agents) runs one task, the workflows plane runs a whole DAG:
parallel fan-out, sequential stages, data flow between tasks, and bounded autonomous
`type: harness` missions.
## 10.1 CNL in one page
**CNL** (the YAML workflow language) has a `workflow` with a `name` and a `tasks[]` array. Each task
is either a leaf task or a compound block; a top-level `input:` (sibling of `workflow:`) seeds the
run. Field names are from `orchestration/cnl-engine/context.ts`, `schema-types.ts` and the real
files in `workflows/*.yaml`.
| Field | On | Meaning |
|---|---|---|
| `name` | `workflow` | workflow name |
| `tasks[]` | `workflow` | ordered list of tasks/blocks |
| `type` | task | `task` \| `parallel` \| `sequential` \| `harness` \| `dynamic` \| `batch` \| `debate` (the `TaskType` enum also has the legacy `agent_loop` and internal `output` / `validate`) |
| `id` | task | task id — the key its output is stored under |
| `agent` | leaf task | the agent id to run (must be `status:active`, or the run is fenced 404/403) |
| `prompt` | leaf task | the task input; search agents take the **literal** query |
| `operation` | leaf task | a named operation for a structured tool-agent (e.g. `serpapi-agent` `operation: amazon`) |
| *(agent params)* | leaf task | any extra card parameters as task fields (e.g. `num: 15`, `searchType: shopping`) |
| `subtasks[]` | compound block | children of a `parallel` / `sequential` / … block |
**Data flow.** There is no `depends_on` — a reference *is* the dependency:
- `{input}` injects the workflow-level input.
- `{data:}` injects a prior task's stored output into an LLM prompt. A `sequential` block
runs its `subtasks` in order; a `parallel` block runs them at once (`Promise.all`, no width cap —
see the engine README "Parallelism"). Reference an upstream task and the engine orders around it.
A real 3-task workflow — a parallel search fan-out feeding one LLM synthesis (adapted from
`workflows/amazon-price-comparison.yaml`):
```yaml
workflow:
name: price-check
tasks:
- type: parallel
id: search_phase
subtasks:
- type: task
id: amazon_search
agent: serpapi-agent
operation: amazon
prompt: "{input}"
num: 15
- type: task
id: shopping_search
agent: serper-agent
searchType: shopping
prompt: "{input}"
- type: task
id: recommendation
agent: one-agent
prompt: |
For the search "{input}", pick the top 3 products.
Amazon results: {data:amazon_search}
Shopping results: {data:shopping_search}
Use only links that appear verbatim in the data.
input: "wireless headphones"
```
## 10.2 Inline execute
`POST /v1/workflows/execute` runs one body in three sizes (`routes/workflows.ts`): a bare agent call,
a `workflow:` document, or the JSON twin of either. `Content-Type` decides YAML or JSON; both produce
the same object.
**A bare agent call** — `agent:` plus the agent's own fields, exactly as its README documents them.
The door lifts it to a one-task workflow (`{ workflow: { name: , tasks: [{ id: "call", type: "task", agent, …fields }] } }`;
a pre-built agent becomes a `harness` task with the `prompt` as its goal). Top-level `format:`, `input:`
and `inputs:` stay the wire's, as in every other body; `stream:`, `sync:`, `dry_run:` and `metadata:` are
wire keys too and never reach the agent.
```bash
curl -s -X POST "https://api.plungeai.com/v1/workflows/execute" \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @- <<'EOF'
agent: calculator-tool-agent
expression: "7*6"
format: markdown
EOF
```
The JSON twin is `{"agent":"calculator-tool-agent","expression":"7*6"}` → the JSON envelope below with
the result in `content`. Two refusals guard the lift: `agent:` next to `workflow:`/`tasks:` →
`400 ambiguous_body` (send one); a field named like an engine task key (`id`, `type`, `agent`,
`depends_on`, `mission_ref`) → `400 reserved_field` naming the fields. An unknown agent id →
`404 unknown_agent` here (on `POST /v1/execute`, [16.9](/one-api/bots#169-the-one-door), the same body is
`404 agent_not_found`). Every execute door refuses a body it cannot read, on the negotiated wire:
malformed JSON → `400 invalid_json` (with the parser's position), malformed YAML → `400 invalid_yaml`,
a scalar or list root → `400 invalid_body`; an empty body is *missing* (`missing_workflow`,
`missing_prompt`, `invalid_params`), not malformed.
**A `params:` wrapper.** On the one door, `POST /v1/execute` ([16.9](/one-api/bots#169-the-one-door)), the fields may also
come wrapped in `params: {…}`, the shape the tools plane uses: the door unwraps them into the agent's fields, and a
key inside `params` wins over a bare field of the same name (`routes/execute.ts`). The wrapper carries agent fields
only. `params` naming `agent`, `workflow`, `tasks`, `on_behalf_of` or a wire key (`format`, `input`, `inputs`,
`user_context`, `stream`, `sync`, `dry_run`, `metadata`) is `400 invalid_body`, because those keys stay at the root.
A `params` that is not an object stays an ordinary field. Live-verified 2026-10-14:
```bash
curl -s -X POST "https://api.plungeai.com/v1/execute" \
-H "Authorization: Bearer $PLUNGE_API_KEY" -H 'Content-Type: text/yaml' --data-binary @- <<'EOF'
agent: calculator-tool-agent
params:
expression: "7*6"
format: markdown
EOF
# → # Calculator: 7*6 … ## 2. Result 42
```
**A workflow** — the full `workflow:` document as a `text/yaml` body, or JSON
`{ "workflow":