> ## Documentation Index
> Fetch the complete documentation index at: https://www.plungeai.com/docs/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, orchestration/api-gateway/routes/templates.ts, orchestration/api-gateway/docs/guide-3.0/08-discovery.md | last-synced: 2026-10-05 (One API 2.3.0: search `total`, `credential` on agent cards, templates routes, resolve; earlier: 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,
  "total": 14,
  "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; `total` is every card the filters match, ignoring
`limit`/`offset`. Agent cards also carry `credential` (`{"tier": "platform"}` or
`{"tier": "connection", "provider", "method"}`).

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", ...}}`.

## POST /v1/discovery/resolve

Resolve one plain-language task to ONE tool, its operation and an example call, with a
calibrated confidence — the shortcut when you want a single answer, not a ranked list.
Body: `task` (required), optional `kinds` (≤ 8 card types; default every discoverable
kind), `category`, `status` (default `active`).

```bash
curl -s -X POST https://api.plungeai.com/v1/discovery/resolve \
  -H "Authorization: Bearer ozk_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"task": "convert a PDF to markdown", "kinds": ["agent"]}'
```

Answer: `{outcome: "match" | "uncertain" | "none", tool: {id, kind, type, name, description, category, status},
operation: {name, purpose, gated}, call: {required_params, example_cnl}, confidence, alternatives: [{id, kind,
name, p}], mode}`. `confidence` is `null` on the keyword fallback (the decision model was unavailable). Act on
`match` only; on `uncertain` or `none` fall back to `GET /v1/discovery/search` and read the cards. A body with no
`task` is `400 invalid_json`; `429 rate_limited` carries `Retry-After`.

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

## Templates — `/v1/templates`

Starter workflows, agents and bots (the gallery behind `plungeai_templates` and Studio's template picker).

```bash
curl -s "https://api.plungeai.com/v1/templates?kind=bot&limit=3" -H "Authorization: Bearer ozk_YOUR_KEY"
# 200 {"templates":[{"id","name","kind","category","difficulty","usage_count","description"}],"count":3}

curl -s https://api.plungeai.com/v1/templates/<template id> -H "Authorization: Bearer ozk_YOUR_KEY"
# 200 {"template": {…}, "yaml": "…"}

curl -s -X POST https://api.plungeai.com/v1/templates/<template id>/use \
  -H "Authorization: Bearer ozk_YOUR_KEY" -H "Content-Type: application/json" -d '{"name": "My copy"}'
# 201 {"workflow": {…}}   a saved workflow of the template's kind
```

`kind` is `workflow`, `agent` or `bot` (else `422 invalid_params`); `limit` 1–100, default 50; `category`
filters. Active templates only, featured first. `use` keeps a bot template's own name and names the others
`<template> (copy)` unless you pass `name` (1–512 characters). The copy runs the same save-time lint as
`POST /v1/workflows`: `422 invalid_workflow` (with `errors` and `warnings`) creates nothing;
`404 template_not_found` for an id that is not an active template. CLI: `ocean-cli templates list | get | use`.

## Related

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