One API
Discovery
Discovery is the read-only catalog behind every other plane: agents, skills, plugins, MCP servers, connectors, models, providers and (once you have saved some) workflows. Four routes, all ozk_-authenticated, all backed by the registry service (orchestration/api-gateway/routes/discovery.ts → orchestration/registry/).
8.1 Search — GET /v1/discovery/search
| Param | Values | Notes |
|---|---|---|
q | text | natural-language or keyword query; with q the registry runs hybrid search (semantic + keyword), without it a plain listing |
kind | agents · skills · plugins · mcp-servers · connectors · models · providers · workflows · experts · backgrounds · personas (= digital-twins) | the plural catalog groups (registry.ts KIND_GROUPS) |
type | singular storage type (agent, skill, mcp_server, …) | alternative to kind |
category | e.g. search, google-workspace | exact card category |
status | active (default) | the public catalog shows callable capabilities only; other statuses need an explicit value |
tier | card tier tag | e.g. verified |
mode | hybrid (default with q) · keyword · vector | force one search leg |
fields | summary · full | full adds parameters, demonstrations and prompt_format |
limit, offset | ints | limit is capped by the registry; page with offset |
include | quality | attaches a quality object per agent card — { score, success_rate, runs } or null when the evaluation service has no data (it was null for every card on 2026-09-18) |
Response: { "cards": [...], "count": <cards in this page>, "total": <every match>, "searchMethod": "text" | "hybrid" | "vector", "query": { …the normalised query… } }. count is the page size; total is every card the filters (and, for q, the keyword predicate) match, ignoring limit and offset — page with offset until you have seen total. For a q search the vector leg can add cards the keyword predicate did not match, so total is a floor, never smaller than offset + count.
curl -s "https://api.plungeai.com/v1/discovery/search?q=web+search&kind=agents&limit=3" \
-H "Authorization: Bearer $PLUNGE_API_KEY"curl -s "https://api.plungeai.com/v1/discovery/search?q=llm&kind=agents&limit=2&include=quality" \
-H "Authorization: Bearer $PLUNGE_API_KEY"Catalog sizes on 2026-09-18 (limit=100, first page): agents 92, skills 12, plugins 5, providers 11, mcp-servers 100+, models 100+, connectors 802 as of 2026-09-29 (9 pages of 100 — read total), workflows 0 for a fresh key (saved workflows are per user), experts / backgrounds / personas 0 (capability cards are created by users and missions — see Skills, plugins & capability injection).
8.2 The card object
Summary cards (fields=summary, the default for the agents/tools lists) carry: id, type, name, version, description, category, best_for[], not_for[], tags[], status, icon, color, created_at, updated_at. Full cards add parameters[], demonstrations[], prompt_format, output_type, output. Connector cards add a connector object (auth, provider_key, key_placeholder, help_text); model cards add pricing and capability columns from the rate card.
Tags encode the operational facts the planes act on:
| Tag | Meaning |
|---|---|
status:active | callable — the only status the tools/agents planes accept |
gated:<op> | the trust fence refuses <op> unattended (403) — Tools plane |
tier:verified | reviewed card |
auth:oauth_static · auth:api_key · auth:oauth_dcr | how a connector is connected — Connectors |
serves:agent | a connector some tool agent uses |
8.3 Recommend — POST /v1/discovery/recommend
Body { "type": "agent" | "skill" | … (plural kinds accepted as aliases), "task": "<what you want done>", "status"?: "<status>" }. Since 2026-09-25 recommend rides on the Jev resolve (§8.4) and works again — from 2026-09-18 it answered 502 upstream_error for every request. status defaults to active. Response: { "recommended": "<card id>" | null, "card": { …the card… } | null, "score": <0–1>, "resolve": { …the full resolve decision… } } — recommended is null and score is 0 when nothing fits; score is also 0 on the keyword fallback (no confidence). Only the types in the discovery index can be recommended — agent, skill, connector, mcp_server, workflow, plugin, expert, digital_twin; any other valid type (model, provider, background, component, theme, …) answers 400 type_not_discoverable (legacy aliases such as ai_agent are normalised first), an unknown type 400 invalid_request.
8.4 Resolve — POST /v1/discovery/resolve
One step from a task to a call: which tool, which operation, what command, and how sure the platform is. The registry shortlists cards per kind (BM25 over id, name, description, best_for and tags), one decision request to the Jev model (typesafe-provider) picks a card per kind or none, and the answer carries that card's operation, its required parameters, an example CNL task and a calibrated confidence (orchestration/registry/resolve.ts).
curl -s -X POST "https://api.plungeai.com/v1/discovery/resolve" \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"task": "current weather conditions for Berlin", "kinds": ["agents"]}'The body is whitelisted — any other field is dropped; you never pass an owner or a debug flag, so only public cards are considered.
| Field | Values | Notes |
|---|---|---|
task | text, required | what you want done, in plain language; missing or empty → 400 invalid_json |
kinds | array of card types: agent · workflow · connector · mcp_server · skill · plugin · … | plural kinds (agents, mcp-servers, …) are accepted aliases; deduplicated, at most 8; default: agent, workflow, connector, mcp_server, skill, plugin |
category | e.g. search | exact card category |
status | active (default) · ready · any · a literal status | same default as search: callable capabilities only |
Response fields:
| Field | Meaning |
|---|---|
outcome | match · uncertain · none — see below |
tool | the chosen card: { id, kind, type, name, description, category, status }; absent on none |
operation | { name, purpose?, gated } — absent when the card has several operations and none was decided; gated: true means the trust fence needs an approval |
call | { required_params[], example_cnl? } — the example is a CNL task you can adapt (workflows) |
confidence | 0–1, calibrated; null on the keyword fallback |
alternatives[] | up to 5 runners-up { id, kind, name, p } |
gate | advisory signals from the same request: acts_on_user_system, needs_external_service, prose_suffices (0–1) and a suggested handler |
method | all (every card of each kind was shown to the model) · bm25 (a keyword shortlist) · cache (a stored decision) · lexical-fallback (the decision model was unavailable) · rate-limited (a public caller exceeded the per-isolate Jev budget — the pick is a keyword match with confidence: null) |
pass | 1, or 2 when a second, closer comparison of the top three was needed |
mode | shadow · act — how the platform's own agents use resolve (in shadow they log the decision and keep showing the plain search list); it does not change your answer |
index_version, timings | the catalog snapshot the decision used, and the server-side legs in ms |
What each outcome means for a caller:
match(confidence ≥ 0.8) — calltool.idwithoperation.name; fillcall.required_params(the card at §8.5 has every parameter). Ask your user first whenoperation.gatedistrue.uncertain— the best guess is below 0.8, ormethodislexical-fallbackorrate-limitedand the pick is a keyword match (confidence: null). Treattooland the firstalternativesas a shortlist: read their cards and choose, or ask your user.none— no card in the catalog fits this task, or the task needs no tool at all (plain text answers it). Do not invent an id; answer without a tool or tell your user nothing fits.
Decisions for the same task, kinds, status and category are cached (10 minutes in memory, 6 hours in KV) until the catalog snapshot changes.
8.5 One card — GET /v1/discovery/cards/:type/:id
Returns the full card as markdown with YAML frontmatter (Content-Type: text/markdown) — the same document the platform's own agents read before calling a tool. :type is the singular storage type (agent, skill, plugin, mcp_server, connector, model, provider, workflow, expert, background, digital_twin); the plural kind spellings (agents, mcp-servers, …) are accepted as aliases.
curl -s "https://api.plungeai.com/v1/discovery/cards/agent/brave-agent" \
-H "Authorization: Bearer $PLUNGE_API_KEY"Unknown card → 404 not_found. A 4xx from the registry is normalised into the One API error envelope; a registry 5xx never reaches you raw (502 upstream_error).
8.6 From an id to a call
Card type | Use it as |
|---|---|
agent | GET /v1/tools/<id> + POST /v1/tools/<id>/execute (tools); POST /v1/agents/<id>/execute (agents); agent: <id> in a CNL task (workflows) |
model | "model": "<id>" on POST /v1/chat/completions — ids are provider/model slugs (models); also model: + provider: on an agent-plane call |
provider | the provider/ prefix of model slugs; provider: on agent-plane calls |
skill · plugin · expert · background · digital_twin | skills: [<id>], plugins: [<id>], experts: [<id>], backgrounds: [<id>], persona: <id> on a type: harness task (capability injection) |
mcp_server | POST /v1/mcp/runs with the server id (MCP plane); mcp: [<id>] on a harness mission |
connector | connect it in Studio → Integrations; then the agents that serves:agent it stop returning needs_connection (connectors) |
workflow | POST /v1/workflows/<id>/execute (workflows) |
Search results are cached briefly on the platform side (KV + LRU, ~200 ms per uncached call); agents that discover at run time — concierge, the harness loop — hit this same route, so what you see is what they see.