plungeai-tools-connectors
Tools plane — /v1/tools
Every active agent is also callable as a tool with a machine-checkable invocation contract. The flow is always: discover → inspect the contract → execute. This plane runs the platform's trust fence — gated and money operations refuse unattended execution by design.
Every active agent is also callable as a tool with a machine-checkable invocation contract. The flow is always: discover → inspect the contract → execute. This plane runs the platform's trust fence — gated and money operations refuse unattended execution by design.
Auth: Authorization: Bearer ozk_YOUR_KEY on every route here.
GET /v1/tools — list active tool-agents
Query params: limit (default 50, max 100), offset (default 0).
curl -s "https://api.plungeai.com/v1/tools?limit=2" \
-H "Authorization: Bearer ozk_YOUR_KEY"{
"tools": [
{ "id": "brave-agent", "name": "Brave Search", "type": "agent",
"category": "search", "status": "active",
"description": "Web search via Brave", "tags": ["search"] },
{ "id": "pdf-agent", "name": "PDF Agent", "type": "agent",
"category": "documents", "status": "active",
"description": "Convert and extract PDFs", "tags": ["pdf"] }
],
"count": 2
}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.
GET /v1/tools/{id} — the invocation contract
Fetch this before the first execute of any unfamiliar tool. The contract tells you the operations, which are gated, the required params, the JSON Schema for inputs, and worked examples.
curl -s https://api.plungeai.com/v1/tools/brave-agent \
-H "Authorization: Bearer ozk_YOUR_KEY"{
"agent_id": "brave-agent",
"name": "Brave Search",
"description": "Web search via the Brave Search API",
"operations": [
{ "name": "search", "purpose": "Run a web search", "gated": false,
"required_params": ["query"] },
{ "name": "news", "purpose": "Search recent news", "gated": false,
"required_params": ["query"] }
],
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string" },
"count": { "type": "integer" }
}
},
"examples": [
{ "title": "Basic web search", "cnl": "operation: search\nparams:\n query: cloudflare workers" }
],
"output_type": "markdown"
}404 → {"error":{"code":"unknown_tool","message":"..."}} — the id isn't an
active tool. Re-discover via GET /v1/tools or discovery search.
gated: true on an operation means executing it unattended is refused with
403 refused by this route's pre-dispatch fence (checkAgentCall runs
in 'unattended' mode here, which always resolves a gated-verb/guarded-
category hit to a refusal, never a pause). 409 approval_required is a
different, real code path on this same route — see the fence section below.
POST /v1/tools/{id}/execute — execute an operation
Body — two styles, both valid:
| Field | Type | Notes |
|---|---|---|
operation | string | Operation name from the contract |
params | object | Params per the contract's inputSchema |
prompt | string | Free-text alternative for prompt-driven tools |
format | string | Response negotiation: json (default) | yaml | markdown | text — same values as an Accept header or the request Content-Type mirror |
Validation rules ("reserved" = param names colliding with engine task-envelope
fields — agent, type, id, operation, depth, user_id,
executor_user_id, workflow, execution_id — never forwardable):
- Reserved param names → 422, always.
- Missing required params → 422 only when no
promptis present — a prose prompt satisfies prompt-capable agents, so a prompt-style call skips param validation entirely. The trust fence still runs either way.
Structured style (preferred when the contract defines operations):
curl -s -X POST https://api.plungeai.com/v1/tools/brave-agent/execute \
-H "Authorization: Bearer ozk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"operation": "search", "params": {"query": "cloudflare workers pricing"}}'Prompt style:
curl -s -X POST https://api.plungeai.com/v1/tools/brave-agent/execute \
-H "Authorization: Bearer ozk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "search: cloudflare workers pricing"}'Success — HTTP 200:
{
"ok": true,
"content": "# Web Search: cloudflare workers pricing\n\n1. ...",
"outcome": "success",
"request_id": "00000000-0000-4000-8000-000000000004"
}Error semantics — features, not failures
| Status | Code | Meaning | Correct handling |
|---|---|---|---|
| 400 | invalid_format | format isn't one of json/yaml/markdown/text | Fix the field |
| 404 | unknown_tool | No such active tool | Re-discover the id |
| 422 | invalid_params | Contract violation — response body carries missing, reserved, warnings, and the full contract | Self-correct from the echoed contract; retry once with fixed params |
| 403 | refused (fence) | Gated operation, or payment/blockchain-category agent — never callable unattended | FINAL. Surface verbatim. NEVER retry, rephrase, or work around |
| 403 | refused (lifecycle) | Parked agent (status:developing/development/developed) or unknown card — the reason says which | Re-discover via GET /v1/tools / discovery search |
| 409 | approval_required | The dispatched run's own outcome came back needs_approval (mapped 1:1 via the outcome→HTTP table — guide 3.0 §13.3/§13.5). NOT produced by the pre-dispatch gated-verb/guarded-category fence, which always resolves to 403 on this route (see below) | Approve out-of-band (Studio / an attended surface), then re-issue the identical request |
| 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) | Shrink the payload (e.g. stream large files through a URL param instead of inline file_data) |
| 424 | connection_required / credential_required | The agent needs a connection or key the acting user hasn't set up | Tell the user what to connect (Studio → Connectors); retry after |
| 429 | rate_limited | Per-key limiter — Retry-After header | Back off and retry |
| 502 | execution_failed | The dispatched agent failed — body carries outcome, detail, content | Act on outcome (needs_input, needs_connection, …) before blind-retrying; one retry only for transient outcomes |
| 502 | upstream_error | Registry/engine hop threw (any route in this plane) | One retry with backoff |
| 503 | agent_unavailable | Agent temporarily down | Retry later or pick an alternative |
422 example — the contract comes back to you
{
"error": {
"code": "invalid_params",
"message": "params failed card validation",
"missing": ["query"],
"reserved": [],
"warnings": [],
"contract": { "agent_id": "brave-agent", "operations": [ "..." ] }
}
}Generated code should parse missing and the embedded contract, repair the
body, and retry exactly once. If the second attempt also 422s, stop and report.
403 — the trust fence, and how to tell refusals apart
Fence refusals are FINAL. The gated-verb floor (buy, purchase,
fetch_paid, send, transfer, pay, send_payment, withdraw, shop),
operations the card tags gated:, and payment/blockchain-category agents
(refused regardless of operation on this surface):
{ "error": { "code": "refused", "message": "operation \"transfer\" on x is gated (outward/irreversible) and requires human approval — not available in unattended runs." } }Do not loop, do not reword the prompt to sneak past the fence, do not switch to the prompt style to avoid the operation gate. Tell the user what was refused and why; approvals happen on attended surfaces (Ocean Studio / apps), not through this API.
Lifecycle refusals are not final — the same 403 refused code also covers
parked agents ("parked (status:…) — not callable. Use registry search to find
an active alternative.") and ids with no registry card. The reason string
distinguishes them; the remedy is re-discovery, not surrender.
409 approval_required is real and reachable on this route — but not
from the check above. POST /v1/tools/{id}/execute calls the shared
gated-verb/guarded-category fence (checkAgentCall, core/core-base/ call-agent.ts) in 'unattended' mode, and in that mode every branch that
could otherwise pause for approval (a guarded payment/blockchain category
with no recognizable operation, or a gated verb) resolves to 403 refused
instead — approval_required is only returned by that function in
'interactive' mode, which this route never uses. The 409 you actually see
on this plane comes from a different source: the dispatched run's own
outcome. If the downstream agent/tool run itself pauses mid-execution
(outcome needs_approval — the same status MCP surfaces as a ⏸ block), the
outcome→HTTP mapping (guide 3.0 §13.3, "Trust fence" §13.5) turns that into
409 approval_required here too. Handling: approve out-of-band (Studio
or another attended surface), then re-issue the identical request — the
One API has no continuation token like MCP's plungeai_continue, so you
resend rather than resume.
Tools vs agents — which plane to call
/v1/tools/:id/execute— structured, contract-validated, fenced. Use when you know the operation and params (typical for generated app code)./v1/agents/:id/execute— free prompt, sync/async semantics, no operation contract. Use for open-ended instructions to LLM-driven agents.
Same underlying agents, different invocation disciplines.