One API
Agents plane
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) 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 and bounded autonomous missions on a type: harness task
there (see capability injection).
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 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 — 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 plungeai_continue; on the raw agents plane re-send with approved: true + action_token, or approve by prompt (approve <token>, 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-agentandagentic-agentare frozen legacy ids: they stay callable until the switch-over, then re-point toone-agent. Writeagent: one-agenteverywhere — atype: harnesstask with noagent:line still runs on the legacyharness-agentloop today. The bareharness-agentbinding 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=<n>→{ agents: [...], count, limit, offset, has_more, next_offset }—countis the page size (default limit 50); whenhas_moreis true, fetch the next page atoffset=next_offset. Each card carriesid, a summary andtags(includingstatus:active).GET /v1/agents/categories→{ categories: [{name, count}], count }, aggregated from the active-agent summaries (there is no separate categories store).
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 (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. Two more fields are accepted only as a pair, the approve
round-trip of 9.7: 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:
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)
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).
Model / provider pinning
Pin a model and provider together; discover models with GET /v1/models
(Models plane) or GET /v1/discovery/search?kind=models (Discovery).
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.
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
done9.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.
Relay + encoder: lib/stream-relay.ts (relayEngineStream, openaiEncoder).
Exact request shape:
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):
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:<op> are held for approval:
- A gated action returns
{ "needs_approval": true, "pending_action": {…}, "action_token": "<10-char>", "messages": [...] }. - Re-invoke with the same
messages, plus"approved": trueand theaction_token, to execute and continue. The token is held server-side for 1 hour.ask_userquestions work the same way (append the user's answer tomessages).
On the One API this handshake is surfaced through the MCP plane: 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 <token>, so send {"prompt":"approve <token>"}
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:
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.
| 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) |
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}'