Search

Hybrid keyword and semantic search across apps, skills, knowledge, agents, flows, MCP servers, and published pages.

→ Search concept: collections, suggest vs search, and discovery in the web app


Suggest

GET /suggest or POST /suggest

Unified search for UI autocomplete and discovery. Returns actionable results ranked globally by relevance score across apps, skills, knowledge, agents, MCP servers, and published public pages.

Authentication is optional. Without a token, results are limited to public resources. With a token, your workspace's content is included according to each entry's visibility. See Access control.

Query Parameters (GET)

FieldTypeRequiredDescription
qstringYesSearch query

GET does not accept limit, category, context, or scope — use POST for those fields.

Request Body (POST)

GET only reads q. Use POST for limit, category, context, and scope.

json
1{2  "query": "flux",3  "context": "We were comparing image models for logos.",4  "scope": ["git:github.com/inference-sh/cli", "lang:go"],5  "limit": 10,6  "category": "app"7}
FieldTypeDefaultDescription
querystring—Search query (required). Used for keyword matching
contextstringemptyOptional conversation background (for example the assistant's previous reply from belt hooks). Keyword search uses query only. When present, semantic ranking embeds query + \n\n + context so the prompt leads and longer context does not dominate the vector
scopestring[]emptyEnvironment signals for project-scoped ranking — for example git:github.com/user/repo, lang:go. Appended after query and optional context in the embedding text; does not affect keyword matching. Knowledge entries with matching version.scope tags rank higher; entries without scope are global. belt suggest auto-collects scope from the working directory. See Knowledge API — project scope
limitinteger3 (all categories) or 10 (single category)Per-category cap after global ranking and dedup. When category is empty, each suggest collection (apps, skills, knowledge, agents, MCP servers, pages) may contribute up to limit hits; the response is still one globally ranked list
categorystringempty = allFilter to app, skill, knowledge, agent, mcp, or doc (pages). Invalid values return 400
agentbooleanfalseDeprecated: accepted and ignored. Result descriptions are always clipped to 200 characters

When neither context nor scope is set, semantic ranking embeds query alone. When only scope is set, it embeds query + \n\n + the scope tags, so scoped calls still match what you asked for.

Response

Response:

json
1{2  "query": "flux",3  "results": [4    {5      "type": "app",6      "name": "pruna/flux-dev",7      "description": "Advanced text-to-image generation...",8      "command": "belt app run pruna/flux-dev",9      "score": 0.9210    },11    {12      "type": "skill",13      "name": "myteam/code-review",14      "description": "Guidelines for reviewing pull requests",15      "command": "belt skill use myteam/code-review",16      "score": 0.8517    },18    {19      "type": "knowledge",20      "tag": "reference",21      "name": "myteam/stripe-webhooks",22      "description": "Stripe webhook handling patterns",23      "command": "belt knowledge get myteam/stripe-webhooks",24      "score": 0.8125    }26  ],27  "impression_id": "01JABC1234567890ABCDEFGHIJK"28}
FieldDescription
impression_idWhen results are non-empty, the id of the stored suggest impression. Pass it as ref to POST /suggest/convert when a listed resource is loaded (the belt CLI does this via --ref on run/use/get commands). Omitted when there are no results.
typeResult category: app, skill, knowledge, agent, mcp, or doc (pages)
tagOptional sub-type when the indexed entity type differs from type — for example reference, observation, or blog on a knowledge or page hit. Omitted when redundant with type.
nameDisplay name or namespace/name for entities
descriptionShort summary, clipped to 200 characters (whitespace collapsed, overflow ends with ...)
commandSuggested CLI command or doc URL
scoreRelevance score (0–1)

Results below the relevance floor (default 0.8) are omitted from /suggest responses. Forks with identical content collapse to the highest-scoring hit via content-hash dedup; when the same base name appears under multiple namespaces with different content, only the highest-scoring variant is returned. When searching all collections, each type is hard-capped at limit results so a single collection cannot dominate the list. Lower-scoring hits may still appear in GET /search when you need broader recall.

See REST overview — response format.

Errors

StatusCodeDescription
400invalid_requestMissing query, invalid JSON body, or unknown category
502search_errorSearch backend failure (Meilisearch or embedding service unavailable)
503search_unavailableSearch is not configured on this deployment

Example

bash
1curl "https://api.inference.sh/suggest?q=flux"
bash
1curl -X POST https://api.inference.sh/suggest \2  -H "Content-Type: application/json" \3  -d '{"query": "flux", "limit": 5}'

Suggest convert (attribution)

POST /suggest/convert

Records that a resource from a prior /suggest impression was loaded — for example after belt suggest surfaces a skill and you run belt skill use with the --ref the CLI captured from impression_id. The belt CLI sends this asynchronously; callers should not block user workflows on the response.

Authentication is optional. A conversion row is stored only when ref matches an impression recorded for the caller's workspace. Unknown refs and refs from other workspaces still return success and record nothing.

Request body

json
1{2  "ref": "01JABC1234567890ABCDEFGHIJK",3  "resource_name": "myteam/code-review",4  "resource_type": "skill"5}
FieldTypeDescription
refstringRequired. Full impression id from impression_id (exact match; not a prefix).
resource_namestringRequired. Loaded resource address (for example namespace/name or MCP server id).
resource_typestringOptional category hint (app, skill, knowledge, agent, mcp, …).

Response

StatusDescription
200Success with the standard envelope and empty data ({"data":{},"messages":[]}). The belt CLI treats only 200 as success. Storage failures are logged server-side but still return 200.
400invalid_request — malformed JSON or missing ref / resource_name

Example

bash
1curl -X POST https://api.inference.sh/suggest/convert \2  -H "Authorization: Bearer inf_your_key" \3  -H "Content-Type: application/json" \4  -d '{5    "ref": "01JABC1234567890ABCDEFGHIJK",6    "resource_name": "myteam/code-review",7    "resource_type": "skill"8  }'

Session end (belt plugin telemetry)

POST /session/end

Records aggregate telemetry when a belt plugin agent session ends. The belt CLI calls this from belt plugin session-end on the SessionEnd hook. This is unrelated to app execution sessions (GET /sessions under the apps API).

The route accepts optional authentication. A row is stored only when the caller presents a valid Bearer token (API key or session) for a signed-in user with an active workspace. Anonymous requests still return 200 and record nothing.

Request body

JSON body, maximum 64 KiB.

json
1{2  "session_id": "sess_abc123",3  "agent": "claude-code",4  "messages": 12,5  "user_turns": 4,6  "files_edited": 2,7  "tools": ["Bash", "Read", "mcp__belt__search"]8}
FieldTypeDescription
session_idstringHarness session identifier. Required to persist a row (max 128 bytes).
agentstringAgent runtime name when known (for example claude-code, codex). Max 64 bytes.
messagesintegerConversation messages counted from the transcript (human prompts plus assistant replies with text), not every JSONL line. Negative values are stored as 0.
user_turnsintegerUser turns in the session. Must be greater than zero to persist; otherwise the request is ignored.
files_editedintegerNumber of files touched. Negative values are stored as 0.
toolsstring[]Tool names used (belt and MCP tools). Empty names are dropped. At most 64 tools; each name is capped at 128 bytes.

Older belt plugins may still send cwd, summary, or tags. Those fields are accepted and ignored — only the counts and tools above are stored. No file paths, prompts, replies, or summary text are saved.

When belt plugin session-end runs on SessionEnd, it reads the agent transcript JSONL locally and derives these numbers before calling the API. Claude Code transcripts store the API payload under message; file edits appear in file-history-snapshot entries (snapshot.trackedFileBackups). Counts include human prompts and assistant replies with text, plus tool names from assistant turns — not tool results, injected reminders, attachments, mode changes, sub-agent turns, or other non-conversation lines. Codex rollouts use event_msg / response_item payload types with the same goal (user messages and assistant/tool activity, not noise lines). If there are zero user turns, the CLI skips the request.

One row is kept per (workspace, session_id). If the same session ends again after a resume, the existing row is updated.

Response

StatusDescription
200Success with the standard envelope and empty data ({"data":{},"messages":[]}). The belt CLI treats only 200 as success. Storage failures are logged server-side but still return 200 so session-close hooks are not blocked.
400invalid_request — malformed JSON body

Example

bash
1curl -X POST https://api.inference.sh/session/end \2  -H "Authorization: Bearer inf_your_key" \3  -H "Content-Type: application/json" \4  -d '{5    "session_id": "sess_abc123",6    "agent": "claude-code",7    "messages": 12,8    "user_turns": 4,9    "files_edited": 2,10    "tools": ["Bash", "Read"]11  }'

GET /search or POST /search

Direct search with collection and filter control. Returns scored hits with entity metadata.

Query Parameters (GET)

FieldTypeRequiredDescription
qstringYesSearch query
collectionstringNoOne collection: apps, skills, knowledge, agents, flows, mcp_servers, or pages. Omit to search all collections.

GET reads only q and collection, and returns up to 20 results per collection. Use POST to search several collections or set limit.

Request Body (POST)

json
1{2  "query": "payment processing",3  "collections": ["knowledge", "skills"],4  "limit": 205}
FieldTypeRequiredDescription
querystringYesSearch query
collectionsstring[]NoCollections to search. Omit to search all collections.
limitintegerNoMax results per collection. Default 20.

Response

Response:

json
1{2  "query": "payment processing",3  "results": [4    {5      "collection": "knowledge",6      "id": "know_abc123",7      "score": 0.87,8      "fields": {9        "name": "stripe-webhooks",10        "namespace": "myteam",11        "description": "Stripe webhook handling patterns",12        "type": "reference",13        "parent_id": "know_abc123",14        "updated_at": 177073201915      }16    }17  ]18}

See REST overview — response format.

FieldDescription
collectionSource collection (apps, skills, knowledge, agents, flows, mcp_servers, pages)
idEntity ID
scoreRelevance score (0–1)
fieldsCollection-specific metadata (name, namespace, description, tags, etc.)

Errors

StatusCodeDescription
400invalid_requestMissing query or invalid JSON body
500search_errorSearch backend failure
503search_unavailableSearch is not configured on this deployment

Example

bash
1curl "https://api.inference.sh/search?q=flux&collection=apps"
bash
1curl -X POST https://api.inference.sh/search \2  -H "Authorization: Bearer inf_your_key" \3  -H "Content-Type: application/json" \4  -d '{5    "query": "payment processing",6    "collections": ["knowledge"],7    "limit": 208  }'

Use an API key to search your workspace's content. Public resources are searchable without authentication.


Access control

Search applies the same visibility rules as the REST list endpoints. The index filters by your workspace, user, and organization, and Postgres re-checks the final hits. An out-of-date index can hide a result but never exposes private content.

Marketplace takedowns use the same re-check: if a public app, skill, agent, flow, or MCP server has a store listing that is delisted or suspended, it does not appear in /search or /suggest for other callers (including anonymous users). The owning workspace still finds it in search. Listing status is not stored in the search index, so a takedown takes effect on the next query without reindexing. Store browse endpoints (GET /store/apps, GET /store/skills) already exclude non-live listings.

VisibilityWho sees it in search
publicEveryone, no auth required
teamMembers of the owning workspace
privateOnly the creator, within the owning workspace
orgCallers whose workspace belongs to the same organization
unlistedMembers of the owning workspace. Other callers do not get it from search.

Search never returns other members' private entries to workspace admins. include_private is a listing option and does not apply to search.

Pages are in the pages collection only when they are published and public. Workspace-only and private pages are not indexed.


Collections

CollectionContents
appsApps in the grid (retired apps are filtered out of results)
skillsSkills from the registry and your workspace
knowledgeObservations, references, preferences (requires auth for non-public entries)
agentsAgents in your workspace and public agents
flowsFlow definitions. Returned by /search, not by /suggest.
mcp_serversMCP servers configured for your workspace
pagesPublished public documentation and blog posts

Search combines keyword matching with semantic similarity. Newly created entities are keyword-searchable immediately; semantic ranking improves after background embedding completes.

If query embedding fails or times out, the API returns keyword-only results for that request (no semantic re-ranking).


  • Search concept — when to use suggest vs search and what each collection contains
  • Skills — list, create, and manage skills
  • Knowledge — manage knowledge entries that appear in search

we use cookies

we use cookies to ensure you get the best experience on our website. for more information on how we use cookies, please see our cookie policy.

by clicking "accept", you agree to our use of cookies.
learn more.