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)
| Field | Type | Required | Description |
|---|---|---|---|
q | string | Yes | Search 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.
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}| Field | Type | Default | Description |
|---|---|---|---|
query | string | — | Search query (required). Used for keyword matching |
context | string | empty | Optional 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 |
scope | string[] | empty | Environment 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 |
limit | integer | 3 (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 |
category | string | empty = all | Filter to app, skill, knowledge, agent, mcp, or doc (pages). Invalid values return 400 |
agent | boolean | false | Deprecated: 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:
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}| Field | Description |
|---|---|
impression_id | When 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. |
type | Result category: app, skill, knowledge, agent, mcp, or doc (pages) |
tag | Optional 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. |
name | Display name or namespace/name for entities |
description | Short summary, clipped to 200 characters (whitespace collapsed, overflow ends with ...) |
command | Suggested CLI command or doc URL |
score | Relevance 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
| Status | Code | Description |
|---|---|---|
| 400 | invalid_request | Missing query, invalid JSON body, or unknown category |
| 502 | search_error | Search backend failure (Meilisearch or embedding service unavailable) |
| 503 | search_unavailable | Search is not configured on this deployment |
Example
1curl "https://api.inference.sh/suggest?q=flux"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
1{2 "ref": "01JABC1234567890ABCDEFGHIJK",3 "resource_name": "myteam/code-review",4 "resource_type": "skill"5}| Field | Type | Description |
|---|---|---|
ref | string | Required. Full impression id from impression_id (exact match; not a prefix). |
resource_name | string | Required. Loaded resource address (for example namespace/name or MCP server id). |
resource_type | string | Optional category hint (app, skill, knowledge, agent, mcp, …). |
Response
| Status | Description |
|---|---|
200 | Success 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. |
400 | invalid_request — malformed JSON or missing ref / resource_name |
Example
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.
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}| Field | Type | Description |
|---|---|---|
session_id | string | Harness session identifier. Required to persist a row (max 128 bytes). |
agent | string | Agent runtime name when known (for example claude-code, codex). Max 64 bytes. |
messages | integer | Conversation messages counted from the transcript (human prompts plus assistant replies with text), not every JSONL line. Negative values are stored as 0. |
user_turns | integer | User turns in the session. Must be greater than zero to persist; otherwise the request is ignored. |
files_edited | integer | Number of files touched. Negative values are stored as 0. |
tools | string[] | 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
| Status | Description |
|---|---|
200 | Success 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. |
400 | invalid_request — malformed JSON body |
Example
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 }'Search
GET /search or POST /search
Direct search with collection and filter control. Returns scored hits with entity metadata.
Query Parameters (GET)
| Field | Type | Required | Description |
|---|---|---|---|
q | string | Yes | Search query |
collection | string | No | One 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)
1{2 "query": "payment processing",3 "collections": ["knowledge", "skills"],4 "limit": 205}| Field | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Search query |
collections | string[] | No | Collections to search. Omit to search all collections. |
limit | integer | No | Max results per collection. Default 20. |
Response
Response:
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.
| Field | Description |
|---|---|
collection | Source collection (apps, skills, knowledge, agents, flows, mcp_servers, pages) |
id | Entity ID |
score | Relevance score (0–1) |
fields | Collection-specific metadata (name, namespace, description, tags, etc.) |
Errors
| Status | Code | Description |
|---|---|---|
| 400 | invalid_request | Missing query or invalid JSON body |
| 500 | search_error | Search backend failure |
| 503 | search_unavailable | Search is not configured on this deployment |
Example
1curl "https://api.inference.sh/search?q=flux&collection=apps"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.
| Visibility | Who sees it in search |
|---|---|
public | Everyone, no auth required |
team | Members of the owning workspace |
private | Only the creator, within the owning workspace |
org | Callers whose workspace belongs to the same organization |
unlisted | Members 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
| Collection | Contents |
|---|---|
apps | Apps in the grid (retired apps are filtered out of results) |
skills | Skills from the registry and your workspace |
knowledge | Observations, references, preferences (requires auth for non-public entries) |
agents | Agents in your workspace and public agents |
flows | Flow definitions. Returned by /search, not by /suggest. |
mcp_servers | MCP servers configured for your workspace |
pages | Published 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).
Related APIs
- 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