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

# Discovery — /v1/discovery

> Discovery is the live catalog of everything the platform can do — agents, workflows, MCP servers, connectors, models — with hybrid semantic + keyword search ranked server-side.


<!-- sources-of-truth: orchestration/api-gateway/openapi.ts, orchestration/api-gateway/routes/discovery.ts, docs/ONE-API-DEVELOPER-GUIDE-2.0.md | last-synced: 2026-09-24 (discovery half of the original discovery-and-traces.md; the traces half lives in plungeai-results-traces. Re-verified against routes/discovery.ts: SEARCH_PARAMS whitelist, status defaults to active, 4xx/5xx normalization in forward(), KIND_TO_TYPE aliasing — all match, no drift found) -->
Discovery is the **live catalog** of everything the platform can do — agents,
workflows, MCP servers, connectors, models — with hybrid semantic + keyword
search ranked server-side.

Auth: `Authorization: Bearer ozk_YOUR_KEY` on every route here.

## Why discovery first

Ids, capabilities, and counts change without notice. Any code (especially
generated code) that names an agent, tool, MCP server, or model must have
taken that id from a live discovery/list response — never from memory, docs,
or this skill. Describe the capability in plain words and trust the ranking.

## GET /v1/discovery/search

| Param | Values | Notes |
|---|---|---|
| `q` | free text | Natural-language capability query ("convert pdf to markdown") |
| `kind` | `agents` \| `skills` \| `digital-twins` \| `personas` \| `experts` \| `backgrounds` \| `workflows` \| `models` \| `providers` \| `connectors` \| `plugins` \| `mcp-servers` | Catalog segment (plural) |
| `type`, `category`, `tier` | strings | Additional filters |
| `status` | default `active` | Leave defaulted — active means deployed and callable |
| `limit`, `offset` | integers | Paging |
| `mode` | `hybrid` \| `keyword` \| `vector` | Force one search leg (default hybrid). Response `searchMethod` values are `hybrid`/`vector`/`text` — keyword mode (and any keyword fallback) answers `"text"`, never `"keyword"` |
| `fields` | `list` \| `summary` \| `full` | Card detail level |
| `include` | `quality` | Attach measured eval scores per card |

```bash
curl -s "https://api.plungeai.com/v1/discovery/search?q=web%20search&kind=agents&limit=2" \
  -H "Authorization: Bearer ozk_YOUR_KEY"
```

```json
{
  "cards": [
    { "id": "brave-agent", "name": "Brave Search", "type": "agent",
      "category": "search", "status": "active",
      "description": "Web search via Brave", "tags": ["search", "web"] },
    { "id": "exa-agent", "name": "Exa Search", "type": "agent",
      "category": "search", "status": "active",
      "description": "Semantic web search", "tags": ["search"] }
  ],
  "count": 2,
  "searchMethod": "hybrid"
}
```

The live response also carries a `query` echo object, and extra fields may
appear — parse what you need, don't assert the exact shape. `count` is the
returned page's length, not the catalog total.

With `include=quality`, each card gains a `quality` block —
`{"score": 1, "success_rate": 1, "runs": 12}` — or `null` when unmeasured
(no eval runs yet on that card/tier). Treat `null` as "unknown", not "bad".

`401` → missing/invalid `ozk_` key.

## POST /v1/discovery/recommend

Ask the platform to pick the best card for a task instead of ranking yourself.

Body: `{"type": "<card type, e.g. agent>", "task": "<what you need done>"}`.

```bash
curl -s -X POST https://api.plungeai.com/v1/discovery/recommend \
  -H "Authorization: Bearer ozk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "agent", "task": "summarize a long PDF into bullet points"}'
```

```json
{
  "recommended": "pdf-agent",
  "card": { "id": "pdf-agent", "name": "PDF Agent", "type": "agent",
            "category": "documents", "status": "active",
            "description": "Convert and extract PDFs" },
  "score": 0.91
}
```

`recommended` (and `card`) can be **`null`** when nothing matches — handle it;
a card does not always come back.

Errors — always the One API envelope (`{"error": {"code", "message", ...}}`),
but the `code` differs by where the rejection happened:

- Unparseable body → `400 {"error":{"code":"invalid_json","message":"JSON body required: { type, task }"}}`.
- A parseable body the registry rejects → normalized to `400
  {"error":{"code":"invalid_request","message":"Invalid type: x"}}` /
  `{"code":"invalid_request","message":"Task description required"}` — the
  registry's own flat `{"error": "<string>"}` is rewritten into the envelope
  before it reaches you (`message` carries the original text verbatim); it
  never reaches the caller as a bare string. Always safe to read
  `err.error.code`.
- `429` → `rate_limited` (`Retry-After` header).
- `5xx` from the registry never leaks through raw — it becomes a clean
  `502 {"error":{"code":"upstream_error", ...}}`.

## GET /v1/discovery/cards/{type}/{id}

The full capability card as **markdown** (`text/markdown`) — the LLM-friendly
view with operations, parameters, good-at examples, and "Not for → use X
instead" redirects. Fetch it before building on an unfamiliar capability.
`{type}` is the singular card type (`agent`, `skill`, `digital_twin`, `expert`,
`background`, `workflow`, `model`, `provider`, `connector`, `plugin`,
`mcp_server`) — the plural search `kind` values are accepted aliases.

```bash
curl -s https://api.plungeai.com/v1/discovery/cards/agent/brave-agent \
  -H "Authorization: Bearer ozk_YOUR_KEY"
```

```markdown
# Brave Search (brave-agent)

Web search via the Brave Search API.

## Operations
- search — Run a web search (params: query, count?)
...
```

`404` when no such card exists — normalized to the One API envelope,
`{"error":{"code":"not_found","message":"Card not found"}}`; a bad `{type}`
is `400 {"error":{"code":"invalid_request","message":"Invalid type: …"}}`.
The registry's own flat `{"error": "<string>"}` response is rewritten into
this envelope (the original text lands in `message`) before it ever reaches
you — same normalization as `/v1/discovery/recommend` above.

## Related

Execution trace lookups (`GET /v1/traces/{id}`) are a separate observability
surface — see the `plungeai-results-traces` skill, not this one.
