> ## Documentation Index
> Fetch the complete documentation index at: https://docs.plungeai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK & codegen — typed clients, snippets, and the AGENTS.md block

> The supported integration stance, in order of preference:


<!-- sources-of-truth: orchestration/api-gateway/openapi.ts, orchestration/api-gateway/routes/discovery.ts, orchestration/api-gateway/routes/workflows.ts, sdk/one-api-client/README.md, docs/ONE-API-DEVELOPER-GUIDE-2.0.md | last-synced: 2026-09-24 (re-verified: `/v1/workflows/execute-stream` is now a full entry in openapi.ts — the "absent from spec" gap is closed, claim removed; discovery.ts `forward()` normalizes recommend/cards flat registry errors into the standard envelope, so the "passes through raw" claim was stale and is corrected) -->
The supported integration stance, in order of preference:

1. **Plain HTTPS** — every plane is JSON over HTTPS; any HTTP client works.
2. **The OpenAI SDK you already use** — for the models plane only
   (`base_url = https://api.plungeai.com/v1`, `sk-ocean-` key). See the `plungeai-models`
   skill.
3. **Generate your own client** from the live OpenAPI 3.1 spec — the primary typed path
   today.
4. **`@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**:

```bash
npx openapi-typescript https://api.plungeai.com/v1/openapi.json -o src/plungeai-schema.d.ts
npm install openapi-fetch
```

```ts
import 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:

```python
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`](https://openapi-ts.dev); runtime is
[`openapi-fetch`](https://openapi-ts.dev/openapi-fetch) (~6KB). The only handwritten code is
a ~15-line `createOneApi()` factory.

```ts
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:

```ts
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.

```bash
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 payload
- `GET https://api.plungeai.com/v1/openapi.json` — the machine contract
- `GET 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:

```markdown
## 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.
