{"openapi":"3.1.0","info":{"title":"agentkit.md public API","version":"1.0.0","summary":"Keyless REST API for the agent-ready plugin registry.","description":"agentkit.md is a public, agent-ready registry of plugin cards: structured, AI-generated profiles of agent-harness plugins (what a plugin does, how to install it, which platforms and agents it supports), served over a keyless REST API, an MCP server, and a searchable catalog.\n\nAll endpoints are public and keyless: no registration, no API keys, no OAuth. See https://agentkit.md/auth.md for the full auth posture and https://agentkit.md/docs for the human-readable developer portal.\n\n**Versioning / deprecation policy:** The API is versioned by URL path prefix (/api/v1). Breaking changes ship only under a new major prefix (/api/v2) and are never made in place on /api/v1. When an endpoint or parameter is deprecated it keeps working, and its responses carry Deprecation: true plus a Sunset: <HTTP-date> header naming the removal date — at least 90 days after the deprecation is announced in the developer portal (/docs). After the Sunset date the endpoint may answer 410 Gone or be removed. Non-breaking additions — new optional query parameters, new response fields, new endpoints — may land in v1 at any time, so write clients that ignore unknown fields.\n\n**Markdown twins:** every content endpoint also answers at path + \".md\" with text/markdown (e.g. https://agentkit.md/api/v1/plugins.md, https://agentkit.md/api/v1/cards/{key}.md); each response opens with a YAML frontmatter block (title, description, canonical, last-updated).","contact":{"name":"agentkit.md","email":"hello@agentkit.md","url":"https://agentkit.md"}},"servers":[{"url":"https://agentkit.md","description":"Production"}],"security":[],"tags":[{"name":"catalog","description":"Browse and fetch plugin cards."},{"name":"search","description":"Rank plugin cards by relevance to a query."},{"name":"meta","description":"Corpus metadata and API discovery."},{"name":"docs","description":"Documentation surface for the API itself."},{"name":"nlweb","description":"NLWeb natural-language query protocol (/ask)."}],"paths":{"/ask":{"get":{"operationId":"nlweb_ask","tags":["search"],"summary":"NLWeb natural-language ask","description":"Natural-language query endpoint following Microsoft's NLWeb conventions (https://github.com/microsoft/NLWeb). Returns an NLWeb search_results envelope over the plugin-card corpus. Query parameters: q (required, non-empty), limit (1-50, default 10), and the streaming controls — prefer=streaming, prefer[streaming]=true, prefer.streaming=true, or the shorthand streaming=true — which switch the response to a text/event-stream with start / result / complete events. Markdown twin at /ask.md.","security":[],"parameters":[{"name":"q","in":"query","required":true,"description":"Natural-language query. Must be non-empty.","schema":{"type":"string","minLength":1}},{"name":"limit","in":"query","required":false,"description":"Maximum results, 1-50. Default 10.","schema":{"type":"integer","default":10,"minimum":1,"maximum":50}},{"name":"prefer","in":"query","required":false,"description":"Set to \"streaming\" to receive server-sent events instead of JSON. Equivalent forms: prefer[streaming]=true, prefer.streaming=true, streaming=true.","schema":{"type":"string"}},{"name":"streaming","in":"query","required":false,"description":"Shorthand for prefer=streaming: streaming=true returns the SSE form.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"The NLWeb search_results envelope (application/json), or an SSE stream (text/event-stream) when streaming is requested.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NlWebResponse"}},"text/event-stream":{"schema":{"type":"string","description":"Server-sent events: one start event (meta + query), one result event per hit, one complete event."}}}},"400":{"description":"Missing/empty query (code EMPTY_QUERY) or an invalid request (codes INVALID_BODY, INVALID_QUERY).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"nlweb_ask_post","tags":["search"],"summary":"NLWeb natural-language ask (POST body form)","description":"Body variant of GET /ask for NLWeb clients: { query: string, limit?: integer, prefer?: { streaming?: boolean } }. With prefer.streaming true the response is a text/event-stream (start / result / complete events); otherwise the NLWeb search_results envelope.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NlAskRequest"}}}},"responses":{"200":{"description":"The NLWeb search_results envelope (application/json), or an SSE stream (text/event-stream) when prefer.streaming is true.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NlWebResponse"}},"text/event-stream":{"schema":{"type":"string","description":"Server-sent events: one start event (meta + query), one result event per hit, one complete event."}}}},"400":{"description":"Missing/empty query (code EMPTY_QUERY) or an invalid request (codes INVALID_BODY, INVALID_QUERY).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/plugins":{"get":{"operationId":"list_plugins","tags":["catalog"],"summary":"List plugin cards","description":"Returns a filtered, paginated list of plugin cards. All filters are optional and matched case-insensitively. Paginate by offset (limit + offset) or by cursor (opaque next_cursor token, null on the last page); the response envelope is { total, limit, offset, next_cursor, results }.","security":[],"parameters":[{"name":"harness","in":"query","required":false,"description":"Filter by harness id (e.g. herdr). See /api/v1/harnesses.","schema":{"type":"string"}},{"name":"surface","in":"query","required":false,"description":"Filter by integration surface. One of: pane, action, event, startup, external, unclear.","schema":{"type":"string","enum":["pane","action","event","startup","external","unclear"]}},{"name":"platform","in":"query","required":false,"description":"Filter by supported platform. One of: macos, linux, windows, unknown.","schema":{"type":"string","enum":["macos","linux","windows","unknown"]}},{"name":"agent","in":"query","required":false,"description":"Filter by agent CLI. One of: claude, codex, grok, opencode, cursor, devin, any, none.","schema":{"type":"string","enum":["claude","codex","grok","opencode","cursor","devin","any","none"]}},{"name":"tag","in":"query","required":false,"description":"Filter by tag (matches raw_tags and purposes_guess).","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, 1-200. Default 50.","schema":{"type":"integer","default":50,"minimum":1,"maximum":200}},{"name":"offset","in":"query","required":false,"description":"Number of results to skip. Default 0. Ignored when cursor is provided.","schema":{"type":"integer","default":0,"minimum":0}},{"name":"cursor","in":"query","required":false,"description":"Opaque pagination cursor from a previous response's next_cursor (base64url-encoded offset). Pass it back unmodified; null on the last page means no more results.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of plugin cards with pagination metadata. next_cursor is null on the last page; pass it back as the cursor parameter to fetch the next page.","content":{"application/json":{"schema":{"type":"object","required":["total","limit","offset","next_cursor","results"],"properties":{"total":{"type":"integer","description":"Total matching cards across all pages."},"limit":{"type":"integer","description":"Requested page size."},"offset":{"type":"integer","description":"Requested offset."},"next_cursor":{"type":["string","null"],"description":"Opaque cursor for the next page (base64url-encoded offset), or null when this is the last page."},"results":{"type":"array","items":{"$ref":"#/components/schemas/PluginCard"}}}}}}},"400":{"description":"Invalid or missing parameters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded. RateLimit headers are exhausted; Retry-After states the cool-down.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/cards/{key}":{"get":{"operationId":"get_card","tags":["catalog"],"summary":"Get one plugin card","description":"Returns the full card for one plugin. Card keys are owner/repo pairs and therefore contain a slash (e.g. 0cv/herdr-mobile-relay) — URL-encode or pass them directly in the path. Unknown keys return 404 with code CARD_NOT_FOUND.","security":[],"parameters":[{"name":"key","in":"path","required":true,"description":"Card key in owner/repo form, e.g. 0cv/herdr-mobile-relay. The slash is part of the key.","schema":{"type":"string"},"examples":{"0cv/herdr-mobile-relay":{"value":"0cv/herdr-mobile-relay"}}}],"responses":{"200":{"description":"The full plugin card.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PluginCard"}}}},"404":{"description":"No card exists with that key (code CARD_NOT_FOUND).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded. RateLimit headers are exhausted; Retry-After states the cool-down.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/search":{"get":{"operationId":"search_plugins","tags":["search"],"summary":"Search plugin cards","description":"Fuzzy server-side search across the full corpus (name, key, tags, capabilities, description — weighted in that order). Returns the matched cards with a relevance score in [0,1] and the server-side tookMs.","security":[],"parameters":[{"name":"q","in":"query","required":true,"description":"Natural-language or keyword query. Must be non-empty.","schema":{"type":"string","minLength":1}},{"name":"limit","in":"query","required":false,"description":"Maximum results, 1-50. Default 10.","schema":{"type":"integer","default":10,"minimum":1,"maximum":50}}],"responses":{"200":{"description":"Ranked search results with timing.","content":{"application/json":{"schema":{"type":"object","required":["query","tookMs","results"],"properties":{"query":{"type":"string"},"tookMs":{"type":"integer","description":"Server-side search time in milliseconds."},"results":{"type":"array","items":{"type":"object","required":["card","score"],"properties":{"card":{"$ref":"#/components/schemas/PluginCard"},"score":{"type":"number","description":"Relevance in [0,1]; higher is better."}}}}}}}}},"400":{"description":"Invalid or missing parameters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded. RateLimit headers are exhausted; Retry-After states the cool-down.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"search_plugins_post","tags":["search"],"summary":"Search plugin cards (POST body form)","description":"Body variant of GET /api/v1/search for clients that prefer a JSON body: { q: string, limit?: integer 1-50 }. Honors an optional Idempotency-Key header: it is echoed in the response, and repeated keys within 60 seconds replay the first successful response (per-isolate best-effort window).","security":[],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional unique key for this request (max 200 chars). Echoed in the response as idempotency-key; repeating the same key within 60 seconds replays the first successful response instead of re-running the search (per-isolate, best-effort). Failed responses are never replayed.","schema":{"type":"string","maxLength":200},"example":"8f3c1d2a-9b4e-4c7f-a1d2-3e5f6a7b8c9d"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["q"],"properties":{"q":{"type":"string","minLength":1,"description":"Natural-language or keyword query. Must be non-empty."},"limit":{"type":"integer","minimum":1,"maximum":50,"default":10,"description":"Maximum results. Default 10."}}}}}},"responses":{"200":{"description":"Ranked search results with timing. Carries an idempotency-key response header when the request included one.","content":{"application/json":{"schema":{"type":"object","required":["query","tookMs","results"],"properties":{"query":{"type":"string"},"tookMs":{"type":"integer","description":"Server-side search time in milliseconds."},"results":{"type":"array","items":{"type":"object","required":["card","score"],"properties":{"card":{"$ref":"#/components/schemas/PluginCard"},"score":{"type":"number","description":"Relevance in [0,1]; higher is better."}}}}}}}}},"400":{"description":"Invalid JSON body, empty q, or an over-long Idempotency-Key (codes INVALID_BODY, EMPTY_QUERY, INVALID_IDEMPOTENCY_KEY).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded. RateLimit headers are exhausted; Retry-After states the cool-down.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/stats":{"get":{"operationId":"get_stats","tags":["meta"],"summary":"Corpus statistics","description":"Aggregate counts over the whole card corpus: totals, per-harness, per-surface, per-platform, per-agent, and per-quality breakdowns, plus generatedAt (when the bundle was built).","security":[],"responses":{"200":{"description":"Corpus statistics.","content":{"application/json":{"schema":{"type":"object","required":["total","harnesses","bySurface","byPlatform","byAgent","byQuality","generatedAt"],"properties":{"total":{"type":"integer"},"harnesses":{"type":"object","additionalProperties":{"type":"integer"}},"bySurface":{"type":"object","additionalProperties":{"type":"integer"}},"byPlatform":{"type":"object","additionalProperties":{"type":"integer"}},"byAgent":{"type":"object","additionalProperties":{"type":"integer"}},"byQuality":{"type":"object","additionalProperties":{"type":"integer"}},"generatedAt":{"type":"string","description":"ISO timestamp of the card bundle build."}}}}}},"429":{"description":"Rate limit exceeded. RateLimit headers are exhausted; Retry-After states the cool-down.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/harnesses":{"get":{"operationId":"list_harnesses","tags":["meta"],"summary":"List harnesses","description":"The harness dimension values the registry indexes today with per-harness card counts. All current cards are Herdr plugins; this endpoint exists so multi-harness clients do not hardcode that fact.","security":[],"responses":{"200":{"description":"Harness dimension values with counts.","content":{"application/json":{"schema":{"type":"object","required":["harnesses","counts"],"properties":{"harnesses":{"type":"array","items":{"type":"string"}},"counts":{"type":"object","additionalProperties":{"type":"integer"}}}}}}},"429":{"description":"Rate limit exceeded. RateLimit headers are exhausted; Retry-After states the cool-down.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/openapi.json":{"get":{"operationId":"get_openapi","tags":["docs"],"summary":"This OpenAPI document.","description":"Machine-readable description of the public API. Referenced from /.well-known/api-catalog as service-desc.","security":[],"responses":{"200":{"description":"OpenAPI 3.1 document.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/docs":{"get":{"operationId":"get_docs","tags":["docs"],"summary":"Developer portal (HTML).","description":"Human-readable developer portal: quickstart, endpoint reference with curl examples, pagination (offset + cursor), idempotency, rate limits, the error format, and the versioning/deprecation policy. Markdown twin at /docs.md; auth posture at /auth.md; MCP server at https://agentkit.md/mcp.","security":[],"responses":{"200":{"description":"The developer portal as a standalone HTML page.","content":{"text/html":{}}}}}}},"components":{"schemas":{"PluginCard":{"type":"object","description":"A structured, AI-generated profile of one agent-harness plugin: what it does, how to install it, and which platforms and agents it supports. v1 responses additionally carry the registry dimensions harness and model.","required":["key","name","description","description_full","excerpt","install","surface","platforms","agents","integrations","raw_tags","purposes_guess","evidence","quality"],"properties":{"key":{"type":"string","description":"Owner/repo identifier. Use it in the /api/v1/cards/{key} path."},"name":{"type":"string","description":"Human-readable plugin name."},"description":{"type":"string","description":"One-line summary of what the plugin does."},"description_full":{"type":"string","description":"Longer description with more detail on behavior."},"excerpt":{"type":"string","description":"Short excerpt from the source README used as evidence."},"install":{"type":["string","null"],"description":"Install instruction for the harness, or null when unknown."},"surface":{"type":"string","enum":["pane","action","event","startup","external","unclear"],"description":"How the plugin integrates with the harness: pane, action, event, startup, external, or unclear."},"platforms":{"type":"array","items":{"type":"string","enum":["macos","linux","windows","unknown"]},"description":"Supported operating systems."},"agents":{"type":"array","items":{"type":"string","enum":["claude","codex","grok","opencode","cursor","devin","any","none"]},"description":"Agent CLIs the plugin is known to work with."},"integrations":{"type":"array","items":{"type":"string"},"description":"External services the plugin integrates with (e.g. GitHub, Linear)."},"raw_tags":{"type":"array","items":{"type":"string"},"description":"Tags harvested verbatim from the repository."},"purposes_guess":{"type":"array","items":{"type":"string","enum":["notify","remote","agents","worktree","review","forge","layout","navigate","files","cost","monitor","finder","automation","session","naming","text","meta","other"]},"description":"Normalized purpose categories inferred for the plugin."},"evidence":{"type":"array","items":{"type":"string"},"description":"Source excerpts backing the generated claims."},"quality":{"type":"string","enum":["thin","ok","solid"],"description":"How much evidence supports this card."},"harness":{"type":"string","description":"The harness this plugin targets (currently herdr)."},"model":{"type":"string","description":"The model that generated the card."}}},"Problem":{"type":"object","description":"RFC 9457 problem detail returned by every error response, as application/problem+json. The code field is a stable machine-readable identifier; detail is human-readable; issues is present on 400 validation failures.","required":["type","title","status","code"],"properties":{"type":{"type":"string","description":"URI identifying the problem type; \"about:blank\" means the HTTP status says it all."},"title":{"type":"string","description":"Short human-readable summary."},"status":{"type":"integer","description":"HTTP status code, repeated in the body."},"code":{"type":"string","description":"Stable machine-readable error code, e.g. CARD_NOT_FOUND or RATE_LIMIT_EXCEEDED."},"detail":{"type":"string","description":"Human-readable explanation of this occurrence."},"instance":{"type":"string","description":"Path of the request that produced the problem."},"issues":{"type":"array","description":"Validation issues (zod) present on 400 responses with code INVALID_QUERY or INVALID_BODY.","items":{"type":"object","additionalProperties":true}}}},"NlAskRequest":{"type":"object","required":["query"],"properties":{"query":{"type":"string","minLength":1,"description":"Natural-language query. Must be non-empty."},"limit":{"type":"integer","minimum":1,"maximum":50,"default":10,"description":"Maximum results. Default 10."},"prefer":{"$ref":"#/components/schemas/NlAskPrefer"}}},"NlAskPrefer":{"type":"object","description":"NLWeb preference hints. Only streaming is interpreted; unknown keys are accepted and ignored.","properties":{"streaming":{"type":"boolean","description":"When true the response is a text/event-stream of start / result / complete events instead of JSON."}},"additionalProperties":true},"NlWebResult":{"type":"object","required":["name","url","description","score"],"properties":{"name":{"type":"string","description":"Plugin name."},"url":{"type":"string","format":"uri","description":"Canonical card URL on agentkit.md (/api/v1/cards/{key})."},"description":{"type":"string","description":"One-line summary of the plugin."},"score":{"type":"number","description":"Relevance in [0,1]; higher is better."},"schema_object":{"type":"object","description":"Embedded schema.org-style object describing the result.","additionalProperties":true}}},"NlWebResponse":{"type":"object","description":"NLWeb response envelope: response metadata, the echoed query, and ranked results.","required":["_meta","query","results"],"properties":{"_meta":{"type":"object","required":["response_type","version"],"properties":{"response_type":{"type":"string","const":"search_results"},"version":{"type":"string","const":"1.0"}}},"query":{"type":"object","required":["text"],"properties":{"text":{"type":"string","description":"The echoed query text."}}},"results":{"type":"array","items":{"$ref":"#/components/schemas/NlWebResult"}}}}}}}