plungeai-api-setup
SDK & codegen — typed clients, snippets, and the AGENTS.md block
The supported integration stance, in order of preference:
The supported integration stance, in order of preference:
- Plain HTTPS — every plane is JSON over HTTPS; any HTTP client works.
- The OpenAI SDK you already use — for the models plane only
(
base_url = https://api.plungeai.com/v1,sk-ocean-key). See theplungeai-modelsskill. - Generate your own client from the live OpenAPI 3.1 spec — the primary typed path today.
@plungeai/one-api— typed TypeScript client for the execution planes, when published.
There is no hand-maintained SDK, deliberately: the OpenAPI document at
GET /v1/openapi.json is the contract, and everything typed is generated from it.
Generate your own client (any language)
The spec is OpenAPI 3.1 at https://api.plungeai.com/v1/openapi.json. Any 3.1-capable
generator works.
TypeScript:
npx openapi-typescript https://api.plungeai.com/v1/openapi.json -o src/plungeai-schema.d.ts
npm install openapi-fetchimport createClient from 'openapi-fetch'
import type { paths } from './plungeai-schema'
export const plungeai = createClient<paths>({
baseUrl: 'https://api.plungeai.com',
headers: { Authorization: `Bearer ${process.env.PLUNGEAI_API_KEY}` },
})Re-run the generator when the API changes shape — never patch generated types by hand. One
thing to know: /v1/discovery/recommend and /v1/discovery/cards/{type}/{id} normalize the
registry's own flat {"error":"<string>"} rejections into the standard envelope
({"error":{"code","message"}}, code is not_found on a 404, invalid_request otherwise)
before you ever see them — a generated client's error type for these two routes is accurate
as-is, no special-casing needed.
Python — plain requests is usually enough; a minimal wrapper:
import os, requests
class PlungeAI:
BASE = "https://api.plungeai.com"
def __init__(self, key: str | None = None):
self.s = requests.Session()
self.s.headers["Authorization"] = f"Bearer {key or os.environ['PLUNGEAI_API_KEY']}"
def _call(self, method: str, path: str, **kw):
r = self.s.request(method, f"{self.BASE}{path}", **kw)
if r.status_code in (403, 409):
err = r.json()["error"]
# Lifecycle refusals (parked agent / unknown card — the reason says)
# are fixable by re-discovering the id; fence refusals are FINAL.
if "registry search" in err.get("message", ""):
raise LookupError(err) # re-discover the id, then retry
raise PermissionError(err) # trust fence — final, never retry
r.raise_for_status()
return r.json()
def execute_agent(self, agent_id: str, prompt: str, sync: bool = True):
return self._call("POST", f"/v1/agents/{agent_id}/execute",
json={"prompt": prompt, "sync": sync})
def search(self, q: str, **params):
return self._call("GET", "/v1/discovery/search", params={"q": q, **params})
ai = PlungeAI()
print(ai.execute_agent("llm-agent", "Say exactly: py ok")["content"])For a fully typed Python client: openapi-python-client generate --url https://api.plungeai.com/v1/openapi.json.
@plungeai/one-api — typed TypeScript client
Not yet published to npm — npm install @plungeai/one-api fails today; generate the
identical client yourself (previous section — it is the same two libraries). When
published, the package is generated from the live GET /v1/openapi.json via
openapi-typescript (opens in a new tab); runtime is
openapi-fetch (opens in a new tab) (~6KB). The only handwritten code is
a ~15-line createOneApi() factory.
import { createOneApi } from '@plungeai/one-api'
const client = createOneApi({ apiKey: process.env.PLUNGEAI_API_KEY! }) // ozk_YOUR_KEY
// GET /v1/tools — response fully typed
const { data, error } = await client.GET('/v1/tools', {
params: { query: { limit: 10 } },
})
if (data) console.log(data.count, data.tools.map(t => t.id))
// POST /v1/agents/{id}/execute — path params and body typed
const { data: exec } = await client.POST('/v1/agents/{id}/execute', {
params: { path: { id: 'llm-agent' } },
body: { prompt: 'Say exactly: sdk ok', sync: true },
})
console.log(exec?.content) // "sdk ok"Schema types are exported for reuse:
import type { paths, components } from '@plungeai/one-api'
type Card = components['schemas']['Card']Every client.GET/POST/DELETE(...) maps 1:1 to a route in the OpenAPI document. The models
plane is not this client's job — use the OpenAI SDK for that.
curl cookbook
Agent/tool ids below (brave-agent, llm-agent) are illustrative — take real ids from the
discovery call two lines up, never from memory.
BASE=https://api.plungeai.com
AUTH='Authorization: Bearer ozk_YOUR_KEY'
curl -s $BASE/health # liveness, no auth
curl -s $BASE/v1/openapi.json | head -50 # live contract, no auth
curl -s "$BASE/v1/discovery/search?q=web%20search&limit=3" -H "$AUTH"
curl -s "$BASE/v1/tools?limit=5" -H "$AUTH"
curl -s $BASE/v1/tools/brave-agent -H "$AUTH" # contract (id from discovery)
curl -s -X POST $BASE/v1/agents/llm-agent/execute -H "$AUTH" \
-H 'Content-Type: application/json' -d '{"prompt":"Say: ok"}'
curl -s $BASE/v1/traces/REQUEST_ID_FROM_ABOVE -H "$AUTH"Self-documenting endpoints
The API teaches itself from the domain — useful for onboarding a coding agent with zero prior context:
GET https://api.plungeai.com/llms.txt— index (llmstxt.org format)GET https://api.plungeai.com/llms-full.txt— the full developer guide + route reference as one markdown payloadGET https://api.plungeai.com/v1/openapi.json— the machine contractGET https://api.plungeai.com/docs— human-readable docs page
AI agents at runtime can skip HTTP clients entirely: connect an MCP client to
https://mcp.plungeai.com/v1 with an ozk_ key (see the plungeai-mcp-setup skill).
AGENTS.md block for generated apps
When you generate an app that calls PlungeAI, drop this block into the app's AGENTS.md
(or CLAUDE.md) so future coding agents in that repo handle the API correctly. Paste-ready:
## PlungeAI One API
This app calls the PlungeAI One API at https://api.plungeai.com.
- Auth: `Authorization: Bearer $PLUNGEAI_API_KEY` (an `ozk_` platform key) for
/v1/discovery, /v1/tools, /v1/agents, /v1/workflows, /v1/mcp, /v1/traces.
The OpenAI-compatible models plane (/v1/chat/completions, /v1/embeddings,
/v1/models) uses `$PLUNGEAI_INFERENCE_KEY` (an `sk-ocean-` key) instead.
Keys live in env vars only — never commit or log them.
- Live contract: `GET /v1/openapi.json`. Live catalog: `GET /v1/agents`,
`GET /v1/discovery/search?q=...`. Never hardcode agent/tool/model ids or
counts — discover them, and re-discover on 404 `unknown_tool` /
`workflow_not_found`.
- Sync vs async: agent execute defaults to sync; `sync:false` returns 202 +
`{workflow_id, task_id}` — poll `GET /v1/agents/results/:wf/:task`
(404 `not_ready` is normal in-flight, back off 2s+).
- HARD RULE — trust fences: `403 refused` (a gated/money verb refused
outright) and `409 approval_required` (a human must approve before the
action runs — this is implemented and produced today, not a placeholder)
are both final for now — surface them to the user; never retry, rephrase,
or route around a `403 refused`. For `409`, approve out-of-band (Ocean
Studio, or MCP `plungeai_continue` — the One API alone has no REST
`continue` route) and then re-issue the identical request.
A separate, unrelated `403 agent_not_active` (a parked/inactive agent id)
is a lifecycle state, not a fence — re-discover a fresh id and retry with
that instead.
- `422 invalid_params` echoes the tool contract — self-correct once, then stop.
- Rate limits: execution-plane `429 rate_limited` carries `Retry-After` —
honour it. The models plane's separate `429 rate_limit_exceeded` has no
Retry-After — use a fixed ~1s backoff there instead.
- 502 `upstream_error` / 500 `internal_error` can come from any route —
retry once with backoff.
- Correlation: send `x-trace-id` on writes; debug via `GET /v1/traces/:id`.
- Full docs for agents: `GET https://api.plungeai.com/llms-full.txt`.Keep the block verbatim except the env-var names, which should match the app's conventions.