plungeai-agents
Agents plane — /v1/agents
Execute any active platform agent with a plain prompt. The gateway wraps your call in a one-task workflow, so every execution yields a workflowid + taskid pair — that pair is the handle for async redemption and for traces.
Execute any active platform agent with a plain prompt. The gateway wraps your
call in a one-task workflow, so every execution yields a workflow_id +
task_id pair — that pair is the handle for async redemption and for traces.
Auth: Authorization: Bearer ozk_YOUR_KEY on every route here.
GET /v1/agents — list active agents
Query params: limit (default 50, max 100), offset (default 0).
curl -s "https://api.plungeai.com/v1/agents?limit=2" \
-H "Authorization: Bearer ozk_YOUR_KEY"{
"agents": [
{
"id": "llm-agent",
"name": "LLM Agent",
"type": "agent",
"category": "ai",
"status": "active",
"description": "General-purpose LLM completion agent",
"tags": ["llm", "completion"]
},
{
"id": "brave-agent",
"name": "Brave Search",
"type": "agent",
"category": "search",
"status": "active",
"description": "Web search via Brave",
"tags": ["search", "web"]
}
],
"count": 2
}Each entry is a registry Card (summary fields). count is the returned
page's length, not the catalog total — page until a short page, never until
offset >= count. A failed registry hop returns 502 upstream_error. The
catalog is live — take ids verbatim from this listing or from
GET /v1/discovery/search; never from memory. For richer capability search
(semantic, filtered, quality scores) use the discovery plane — see the
plungeai-discovery skill.
GET /v1/agents/categories — agent categories with counts
Agent categories with active-agent counts, aggregated from the registry (the
registry has no categories endpoint of its own). Not a cache-stats
pass-through — an older version of this route proxied /api/registry/stats
and returned cache statistics instead; that shape is gone.
curl -s https://api.plungeai.com/v1/agents/categories \
-H "Authorization: Bearer ozk_YOUR_KEY"{
"categories": [
{ "name": "search", "count": 12 },
{ "name": "documents", "count": 9 }
],
"count": 2
}categories[] is sorted by count descending, then name. count is the
number of distinct categories, not the total agent count. The aggregation
queries only the first 100 active agents from the registry (one page,
limit=100&offset=0) — on a catalog past 100 active agents this is a
representative sample of category names, not a guaranteed-complete count
over every active agent. A failed registry hop returns 502 upstream_error,
same as GET /v1/agents.
POST /v1/agents/{id}/execute — run an agent
Body fields:
| Field | Type | Notes |
|---|---|---|
prompt | string | The instruction. Required (or input) |
input | string | Alias for prompt |
persona | string | Optional persona overlay |
provider | string | Provider id (anthropic, openai, gemini, …); required alongside a pinned model |
model | string | Optional model override for LLM-driven agents |
maxTokens | integer | Optional completion cap (max_tokens accepted as an alias — OpenAI habits work) |
max_tokens | integer | Alias for maxTokens |
temperature | number | Sampling temperature, forwarded to the provider |
top_p | number | Nucleus sampling probability, forwarded to the provider |
reasoning_effort | string | Reasoning-effort hint (e.g. low/medium/high) for reasoning models |
thinking_level | string | Extended-thinking level hint for models that support it |
system | string | System prompt / instructions for the run |
messages | array | Prior chat messages ([{role, content}]) instead of a single prompt |
sync | boolean | Default true. false → 202 pointer |
stream | boolean | Default false. true → an OpenAI-shaped chat.completion.chunk SSE instead of a JSON result (see "Streaming" below). Ignored on the async pointer path |
format | string | Response negotiation: json (default) | yaml | markdown | text — same values as an Accept header (application/json, text/yaml, text/markdown, text/plain) or the request Content-Type mirror |
Sync (default) — result inline
curl -s -X POST https://api.plungeai.com/v1/agents/llm-agent/execute \
-H "Authorization: Bearer ozk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Summarize in one line: Cloudflare Workers are serverless."}'{
"content": "Cloudflare Workers run code at the edge without managing servers.",
"workflow_id": "00000000-0000-4000-8000-000000000001",
"task_id": "t1",
"request_id": "00000000-0000-4000-8000-000000000004"
}Async — 202 + pointer
Use for long-running prompts, or when your caller can't hold a connection.
curl -s -X POST https://api.plungeai.com/v1/agents/llm-agent/execute \
-H "Authorization: Bearer ozk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Write a 2000-word market analysis of edge computing", "sync": false}'HTTP 202:
{
"workflow_id": "00000000-0000-4000-8000-000000000001",
"task_id": "t1",
"request_id": "00000000-0000-4000-8000-000000000004"
}Streaming (stream: true)
Instead of a JSON result, the 200 response is text/event-stream: an
OpenAI-shaped chat.completion.chunk per line —
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1788574344,"model":"llm-agent","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1788574344,"model":"llm-agent","choices":[{"index":0,"delta":{"content":"391"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1788574344,"model":"llm-agent","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]One primer chunk (delta.role: "assistant"), then a delta.content chunk
per provider token, then a final chunk (delta: {}, finish_reason: "stop"
or "length" when truncated at max_tokens), then a literal data: [DONE]
line. usage is emitted on the final chunk only when the provider reports
the prompt/completion split (often omitted for streamed runs). Comment
keepalives (: OCEAN PROCESSING) may appear every ~15s while idle. Response
header X-Execution-Id carries the execution id for result redemption /
polling regardless of stream. An error before the first byte is a normal
JSON error envelope (not SSE); an error after the first byte is a single
data: {"error":{…}} frame and the stream closes WITHOUT [DONE].
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | missing_prompt / invalid_format | No prompt/input given, or format isn't one of json/yaml/markdown/text |
| 401 | unauthorized | Bad/missing ozk_ key |
| 403 | agent_not_active | The agent card is not status:active (refused pre-dispatch) |
| 404 | unknown_agent | No such agent id in the registry (refused pre-dispatch) |
| 409 | duplicate_execution_id | The x-trace-id header was already used by an earlier run — send a fresh UUID |
| 413 | payload_too_large | Body over 1 MiB (MAX_REQUEST_SIZE) |
| 422 | invalid_params / unknown_model / empty_completion | Missing/invalid input named by the agent; pinned model absent/inactive in the catalog; or the run completed but the model returned no visible content — raise max_tokens ≥ 64 or change the model |
| 424 | connection_required / credential_required | The agent needs a connection or key the user hasn't set up |
| 429 | rate_limited | Retry-After header |
| 502 | engine_error / result_unavailable | Engine dispatch failed, or sync redemption couldn't fetch the stored result |
| 502 | upstream_error | A downstream hop threw (catch-all, any route in this plane) |
| 503 | agent_unavailable | Agent temporarily down |
A 502 result_unavailable on a sync call does NOT always mean the run failed —
the result may land late, and its error body carries workflow_id + task_id.
Redeem with those ids at the results route below before re-running.
GET /v1/agents/results/{workflowId}/{taskId} — redeem an async result
curl -s https://api.plungeai.com/v1/agents/results/00000000-0000-4000-8000-000000000005/t1 \
-H "Authorization: Bearer ozk_YOUR_KEY"Ready — HTTP 200 (StoredResult):
{
"content": "# Edge Computing Market Analysis\n\n...",
"content_type": "text/markdown",
"workflow_id": "00000000-0000-4000-8000-000000000001",
"task_id": "t1"
}Not ready yet — HTTP 404:
{ "error": { "code": "not_ready", "message": "result not available (yet)" } }Results are tenant-scoped: you can only redeem pointers created by your own key's identity.
Polling pattern
404 not_ready is the normal in-flight state. Poll with backoff, cap the wait:
WF=00000000-0000-4000-8000-000000000005; TASK=t1
for i in 1 2 3 4 5 6 7 8; do
sleep $((i * 2))
BODY=$(curl -s -w '\n%{http_code}' \
"https://api.plungeai.com/v1/agents/results/$WF/$TASK" \
-H "Authorization: Bearer ozk_YOUR_KEY")
[ "$(echo "$BODY" | tail -1)" = "200" ] && { echo "$BODY" | sed '$d'; break; }
doneimport time, requests
def redeem(wf, task, key, tries=8):
for i in range(1, tries + 1):
r = requests.get(
f"https://api.plungeai.com/v1/agents/results/{wf}/{task}",
headers={"Authorization": f"Bearer {key}"})
if r.status_code == 200:
return r.json()["content"]
if r.status_code != 404:
r.raise_for_status()
time.sleep(2 * i) # backoff; 404 not_ready is normal
raise TimeoutError("result not ready")Choosing sync vs async
- sync (default): single quick task, interactive callers. Simplest code.
- async (
sync:false): anything that may run long; queue workers; retries survive process restarts because the pointer is durable. - Send an
x-trace-idheader on the execute call and you can watch the run's spans atGET /v1/traces/<your-trace-id>while polling — see theplungeai-results-tracesskill.