# Store

> Browse approved apps, skills, agents, and flows from the public store.

**URL:** https://inference.sh/docs/api/rest/store
**Last updated:** 2026-10-31

---

Browse approved apps, skills, agents, and flows from the public store.

These endpoints power [Apps](https://inference.sh/apps) and the [skill registry](https://inference.sh/skills). They return **store listings** (metadata, category, rank, tags) — not full app manifests or skill instructions.

No API key is required. See [REST overview — response format](/docs/api/rest/overview#response-format).

→ [Search API](/docs/api/rest/search) — semantic discovery across apps, skills, and knowledge  
→ [Skills API](/docs/api/rest/skills) — skill content, versions, and management  
→ [Apps API](/docs/api/rest/apps): your workspace's apps and deploy workflows

---

## List store categories

`GET /store/categories`

Returns every marketplace **category** with display metadata and a live **`count`** of public store listings in that category. Categories are shared across app, skill, MCP, and other store surfaces. The web app [Apps](https://inference.sh/apps) loads `GET /store/categories?type=app` for category chips; [List approved apps](#list-approved-apps) `category` filters and [MCP server](/docs/connectors/mcp-server) tools such as `app_list` and `mcp_list` accept the same slugs.

No API key is required. Results are sorted by `rank` (ascending), then `slug` (ascending). Categories with no matching public listings still appear with `count: 0` so browse UIs can show the full catalog.

### Query parameters

| Parameter | Description |
|-----------|-------------|
| `type` | Optional store resource type: `app`, `agent`, `flow`, `skill`, `knowledge`, or `mcp`. When set, `count` includes only public listings of that type. When omitted, `count` aggregates listings across all store resource types. Unknown values return `400`. |

### Response (`StoreCategoryDTO[]`)

| Field | Description |
|-------|-------------|
| `slug` | Category slug (`image`, `video`, `decision`, `developer`, …) — same values as `category` on [Apps API](/docs/api/rest/apps#app-category) app objects and as `category` filters on [List approved apps](#list-approved-apps) |
| `name` | Display name (for example `Image`, `Decision`) |
| `description` | Short category blurb for browse UIs |
| `icon` | Icon key used by the web app (for example `image`, `split`) |
| `rank` | Sort order in category pickers (lower = earlier) |
| `count` | Number of public store listings in this category (scoped by `type` when provided) |

```bash
curl "https://api.inference.sh/store/categories?type=app"
```

```json
{
  "data": [
    {
      "slug": "image",
      "name": "Image",
      "description": "Generate, edit and understand images.",
      "icon": "image",
      "rank": 10,
      "count": 128
    },
    {
      "slug": "decision",
      "name": "Decision",
      "description": "Classify, route and score with calibrated probabilities.",
      "icon": "split",
      "rank": 65,
      "count": 4
    }
  ]
}
```

Use this endpoint for category sidebars and filters. Listing `category` / `subcategory` fields on `GET /store/apps` still describe each app row; this endpoint is the canonical category catalog. App objects use a subset of these slugs for `category` ([Apps API — App category](/docs/api/rest/apps#app-category)). Skill and connector listings may use additional slugs (for example `developer`, `productivity`) that appear here but not on every app. Store listings can carry **one to three** categories when published; app Grid cards show a primary `category` field derived from the listing. See [Browsing the Grid — Categories](/docs/apps/browsing-grid#categories).

---

## List browseable tags

`GET /store/tags`

Returns app tag slugs that at least **three public apps** carry, with a display **title** and **count**. The web app [Apps](https://inference.sh/apps) loads this list for the **tasks** tag filter in the explore sidebar (wide screens) and the horizontal tag row on small screens — labels are the slug with hyphens replaced by spaces; the `title` field is for API clients and other UIs. Tags on fewer public apps are still valid on apps and in `tag` filters on [List approved apps](#list-approved-apps); they are omitted here until usage crosses that bar.

No API key is required. Results are sorted by `count` (descending), then `slug` (ascending).

### Response (`StoreTagDTO[]`)

| Field | Description |
|-------|-------------|
| `slug` | Tag slug (`text-to-image`, `lora`, …) — same format as `tags` on [Apps API](/docs/api/rest/apps#app-tags) |
| `title` | Human-readable label (for example `Text to Image`). Tags without a curated title use title-cased words from the slug (`virtual-try-on` → `Virtual Try On`). |
| `count` | Number of public apps that carry the tag |

```bash
curl "https://api.inference.sh/store/tags"
```

```json
{
  "data": [
    { "slug": "text-to-image", "title": "Text to Image", "count": 42 },
    { "slug": "open-weights", "title": "Open Weights", "count": 18 }
  ]
}
```

Use this endpoint (not generated SDK enums) for tag pickers, browse surfaces, and display titles. Older SDK releases exported `AppTag` / `AppTagTitle` constants; those were removed in favor of this API.

---

## List approved apps

`GET /store/apps` or `POST /store/apps/list`

Returns apps with an approved store version, sorted by **rank** (descending) when you omit `sort`. Apps with status `retired` are excluded. Other statuses (`active`, `maintenance`, `deprecated`) may appear when the underlying app is public — see [Apps API — App lifecycle status](/docs/api/rest/apps#app-lifecycle-status).

### Query parameters (GET)

| Parameter | Description |
|-----------|-------------|
| `cursor` | Pagination cursor |
| `limit` | Page size (default 10) |
| `direction` | `forward` or `backward` (default `forward`) |
| `filters` | JSON array of `{ "field", "value", "operator" }` filters |
| `sort` | JSON array of `{ "field", "dir" }` sort orders |
| `search` | JSON object `{ "term", "fields", "exact" }` for text search (`term` is the query string) |

POST accepts the same fields in the JSON body.

### Filters

Pass filters as a JSON array on GET (`filters=...`) or in the POST body. Each entry is `{ "field", "value", "operator" }` with `operator` typically `eq`.

| Field | Description |
|-------|-------------|
| `category` | Category slug from [List store categories](#list-store-categories) (`image`, `video`, `audio`, `chat`, `3d`, `decision`, …) — app `category` values are a subset; see [Apps API — App category](/docs/api/rest/apps#app-category) |
| `tag` | Apps whose `tags` array contains the slug (for example `text-to-image`). Matches [Apps API — App tags](/docs/api/rest/apps#app-tags) |

The web app [Apps](https://inference.sh/apps) explore page uses `category` and `tag` filters together with text search.

```bash
curl -X POST https://api.inference.sh/store/apps/list \
  -H "Content-Type: application/json" \
  -d '{
    "limit": 20,
    "filters": [
      { "field": "category", "value": "image", "operator": "eq" },
      { "field": "tag", "value": "text-to-image", "operator": "eq" }
    ]
  }'
```

### Response (`PublicAppStoreDTO`)

| Field | Description |
|-------|-------------|
| `id` | App / listing ID (same as app ID) |
| `namespace`, `name` | App ref (`namespace/name`) |
| `description` | Short description |
| `category`, `subcategory` | Grid grouping |
| `tags` | Task and trait slugs from the app (`text-to-image`, `lora`, …). Same values as `tags` on [Apps API](/docs/api/rest/apps#app-tags) app objects. |
| `images` | App card images |
| `is_featured` | Featured in the Grid |
| `rank` | Sort rank (higher = more prominent) |
| `has_approved_version` | Always `true` for this endpoint |
| `page_id` | Optional docs page link |

```bash
curl "https://api.inference.sh/store/apps?limit=20"
```

```bash
curl -X POST https://api.inference.sh/store/apps/list \
  -H "Content-Type: application/json" \
  -d '{"limit": 20, "search": {"term": "whisper", "fields": ["name", "description"]}}'
```

Pagination matches other cursor list endpoints (`items`, `next_cursor`, `has_next`, …).

---

## Get app store listing

`GET /store/apps/{appId}`

Returns store metadata for one approved app (`AppStoreListingDTO`). Use this when you already have an app ID from a list response.

| Field | Description |
|-------|-------------|
| `allows_private_workers` | Whether the app can run on private engines |
| `allows_cloud_workers` | Whether the app can run on cloud workers |
| `max_concurrency` | Global cap on concurrent runs for this app across the platform (`0` = no cap) |
| `max_concurrency_per_team` | Per-workspace cap on concurrent runs (`0` = no cap) |
| `min_concurrency` | Displacement floor: while this many or fewer **cloud** workers hold the app, other apps cannot evict them (`0` = no floor). It does not start workers. See [Reserved cloud capacity](#reserved-cloud-capacity-min_concurrency) below. |
| `required_feature` | Optional entitlement resource (for example `feature:seedance`) your workspace must have explicitly granted (`enabled: true`) to run this app. A missing entitlement row is denied, not treated as unrestricted. When blocked, task dispatch returns `feature_not_available` (403) with addon plan metadata when an addon unlocks the feature. See [Entitlements API](/docs/api/rest/entitlements#feature-gated-store-apps). |
| `category`, `subcategory`, `tags`, `rank`, `is_featured` | Store presentation metadata |

```bash
curl "https://api.inference.sh/store/apps/app_abc123"
```

### Reserved cloud capacity (`min_concurrency`)

When `min_concurrency` is greater than zero, a worker holding the app is not evicted for another app's task while `min_concurrency` or fewer workers hold it. It is displacement protection only: the scheduler does not start or pre-load workers to reach the number. An app with no worker holding it gets no protected workers until a task loads it.

| Behavior | Detail |
|----------|--------|
| **Cloud workers only** | Private workers are not protected by `min_concurrency` |
| **Same-app tasks** | Workers already running the app can still accept new tasks for that app |
| **Displacement** | A worker can be reclaimed only when more than `min_concurrency` workers currently have the app loaded |
| **Task failure** | If every matching cloud worker holds another app's floor, a queued task waits 2 minutes from when the block is first seen, then fails with `no online worker can run this app` |

It is part of the app's runtime policy, set by the platform per app. Workspaces cannot configure it. See [Workers concept](/docs/concepts/workers#when-no-worker-is-available-yet) and [Task failure messages](/docs/api/rest/tasks#task-failure-messages).

---

## Get app pricing

`GET /store/apps/{appId}/pricing`

Returns the **published** pricing config for the app's current store version (`AppPricing` — CEL expressions, price variables, rendered description). Used by public app pages.

```bash
curl "https://api.inference.sh/store/apps/app_abc123/pricing"
```

→ [Usage-based pricing](/docs/extend/pricing) · [Get task cost](/docs/api/rest/tasks#get-task-cost)

---

## Estimate app cost

`POST /store/apps/{appId}/estimate`

Preview run cost before dispatch. Evaluates pricing expressions that only depend on task inputs (`task_inputs`, `prices`, `fees`, `task_function`). When fee expressions need post-execution data (`outputs`, `elapsed_ms`, `elapsed_seconds`, `resource_fee`, and so on), the API falls back to the app's `estimate` CEL expression if one is configured in `AppPricing`.

No authentication required.

### Request body

| Field | Type | Description |
|-------|------|-------------|
| `input` | object | Task input map (same shape as `POST /run`) |
| `function` | string | Optional function name for multi-function apps |

### Response (`EstimateCostResponse`)

| Field | Type | Description |
|-------|------|-------------|
| `confidence` | string | `exact` (all fees input-based), `range` (`estimate` returned min/max), or `unknown` (post-execution data required) |
| `microcents` | int64 | Set when `confidence` is `exact` |
| `min`, `max` | int64 | Set when `confidence` is `range` |
| `depends_on` | string[] | Post-execution variables needed when `confidence` is `unknown` |
| `estimate_error` | string | Present when an `estimate` expression exists but failed to evaluate; `confidence` is `unknown` and `depends_on` is still populated |
| `pricing_description` | string | Rendered human-readable pricing string |

**Example (exact estimate):**

```bash
curl -X POST "https://api.inference.sh/store/apps/app_abc123/estimate" \
  -H "Content-Type: application/json" \
  -d '{"input": {"n": 4}}'
```

```json
{
  "confidence": "exact",
  "microcents": 2000000,
  "pricing_description": "$0.02 per image"
}
```

**Example (range estimate via `estimate` expression):**

```json
{
  "confidence": "range",
  "min": 150000,
  "max": 1500000,
  "pricing_description": "About $0.0015–$0.015 per run"
}
```

**Example (unknown — output-dependent pricing, no estimate expression):**

```json
{
  "confidence": "unknown",
  "depends_on": ["outputs"]
}
```

**Example (`estimate_error` — estimate expression failed):**

```json
{
  "confidence": "unknown",
  "estimate_error": "no such key: max_tokens",
  "depends_on": ["outputs"]
}
```

→ [Usage-based pricing](/docs/extend/pricing) — authoring `estimate` expressions  
→ [Evaluate CEL pricing](/docs/api/rest/usage#evaluate-cel-pricing) — preview draft formulas with sample `output_meta`

---

## List approved skills

`GET /store/skills` or `POST /store/skills/list`

Returns skills with an approved store listing. Same cursor, filter, sort, and search parameters as [List approved apps](#list-approved-apps).

### Response (`PublicSkillStoreDTO`)

| Field | Description |
|-------|-------------|
| `id` | Skill / listing ID |
| `namespace`, `name` | Skill ref (`namespace/name`) |
| `description` | Short description |
| `category` | Registry grouping |
| `tags` | Listing tags (when set on the skill store listing) |
| `is_featured` | Featured in the registry |
| `rank` | Sort rank |
| `has_approved_version` | Always `true` for this endpoint |

```bash
curl "https://api.inference.sh/store/skills?limit=20&filters=%5B%7B%22field%22%3A%22namespace%22%2C%22value%22%3A%22inferencesh%22%2C%22operator%22%3A%22eq%22%7D%5D"
```

Example `filters` value (URL-encoded above):

```json
[{"field": "namespace", "value": "inferencesh", "operator": "eq"}]
```

---

## Get skill store listing

`GET /store/skills/{skillId}`

Returns store metadata for one skill (`SkillStoreListingDTO`), including `installs` and `uses` counts.

```bash
curl "https://api.inference.sh/store/skills/skill_abc123"
```

To fetch skill **content** (instructions and files), use [Skills API](/docs/api/rest/skills) (`GET /skills/{namespace}/{name}/content`).

---

## List approved agents

`GET /store/agents` or `POST /store/agents/list`

Returns public agents with a live store listing, in listing order, as [`AgentDTO`](/docs/api/rest/agents) objects. Same cursor, filter, sort, and search parameters as [List approved apps](#list-approved-apps); search reads the agent's name and title. `POST /agents/list` lists your workspace's agents instead.

```bash
curl "https://api.inference.sh/store/agents?limit=20"
```

---

## List approved flows

`GET /store/flows` or `POST /store/flows/list`

Returns public flows with a live store listing, in listing order, as `FlowDTO` objects. Same parameters as [List approved apps](#list-approved-apps); search reads the flow's name, title, and description. `POST /flows/list` lists your workspace's flows instead.

```bash
curl "https://api.inference.sh/store/flows?limit=20"
```

---

## Count listings per type

`GET /store/counts`

Returns how many public listings each resource type has (`app`, `agent`, `flow`, `skill`, `knowledge`, `mcp`). A type with none is absent. Explore hides the tabs that would list nothing.

```bash
curl "https://api.inference.sh/store/counts"
```

```json
{ "app": 446, "skill": 128, "mcp": 37, "agent": 12 }
```

---

## Listings when a resource is deleted

When you delete an app, skill (knowledge entry), agent, flow, or MCP server, the platform removes its store listing in the same operation. The listing is marked delisted, disappears from publisher admin views and public store browse APIs, and the listing history records that the resource was deleted. Search indexing removes the resource via the normal delete path.

---

## Marketplace listing pages (public)

`GET /marketplace/{type}/{namespace}/{name}` returns the public detail (`MarketplaceDetailDTO`) for one **live** listing. No API key is required. `type` is `app`, `skill`, `mcp`, `agent`, `flow`, or `knowledge`. Listing slugs are `namespace/name` for most types; MCP listings use `namespace/server_slug`.

```bash
curl "https://api.inference.sh/marketplace/app/inferencesh/whisper"
```

Signed-in callers can report a listing with `POST /marketplace/{type}/{namespace}/{name}/report` (`apps:read`).

---

## Publisher marketplace (authenticated)

Most routes need `apps:read` or `apps:write`. Publisher profile routes need `teams:read` / `teams:write` and the workspace **edit profile** capability. Every route that **changes** a listing (draft, edit, submit, review messages, delist, relist, publish, withdraw) also needs **edit profile** on the listing's workspace, since a listing is the team's public profile; write access on the resource alone returns **403** `team_role_required`. `POST /store/submit` requires a person (a signed-in user or their personal key), not a workspace API key.

### Publishing invitation

New marketplace publish actions are **invitation-only**. Before `POST /store/listings/draft`, `POST /store/check`, or `POST /store/submit`, the API checks the workspace entitlement `feature:marketplace_publish`. A missing or disabled grant returns **403** `feature_not_available`:

> the marketplace takes submissions by invitation for now; join the waitlist to be let in

Reading listings your team already owns (for example `GET /store/listings/{id}`) is not blocked. Ask for access with [Entitlement requests](/docs/api/rest/entitlements#entitlement-requests); the web app's publisher waitlist submits the `infsh/marketplace_waitlist` form for you.

### Publish flow

1. `GET /store/terms`: policy version, acknowledgement checklist, and rule IDs cited by automated findings.
2. `GET /store/publisher` / `PUT /store/publisher`: publisher profile (support, privacy, contact email verification via `/store/publisher/verify-email/*`).
3. `POST /store/listings/draft`: create or fetch the draft listing for a resource (`resource_type`, `resource_id`).
4. `PUT /store/listings/{id}` (or `PATCH`): edit listing-owned fields (tagline, categories, tags, URLs, data-handling answers, use cases).
5. `POST /store/check`: run validation, scan, and policy checks on a resource version without submitting.
6. `POST /store/submit`: submit a version with changelog, license, and attestation (`terms_version`, `policy_version`, data-handling answers, ticked `acks` from `/store/terms`).

`POST /store/check` and `GET /store/versions/{id}` return a `StoreCheckResultDTO`: one row per check (`validation`, `scan`, `policy`, …) with `status`, `summary`, and `findings[]`. Each finding has `code`, `severity` (`block`, `hold`, `warning`, `info`), `title` / `message` / `fix`, an optional policy `rule`, and an optional `key` naming the resource field to change. A version migrated from the old store tables (`origin: migrated`) reports each check it never ran with status `skipped` ("not run: migrated version").

| Code | Severity | When | Fix |
|------|----------|------|-----|
| `NAME_IS_HOSTNAME` | `hold` | The resource would be listed under a hostname-shaped label (for example `mcp.datadoghq.com` after registering an MCP server from a URL with no display name) | Set a human-readable title on the resource (MCP: `title` on `PUT /mcp-servers/{id}`), then re-run `POST /store/check` |

| Need | Endpoint |
|------|----------|
| List your team's listings | `GET /store/listings` or `POST /store/listings/list` |
| Listing detail / edit | `GET /store/listings/{id}`, `PUT /store/listings/{id}` |
| Version history | `GET /store/listings/{id}/versions`, `GET /store/versions/{id}` |
| Activity / review thread | `GET /store/listings/{id}/events`, `POST /store/listings/{id}/messages` |
| Delist / relist | `POST /store/listings/{id}/delist`, `POST /store/listings/{id}/relist` |
| Publish or withdraw a reviewed version | `POST /store/versions/{id}/publish`, `POST /store/versions/{id}/withdraw` |

---

## Store vs search vs resource APIs

| Need | Endpoint |
|------|----------|
| Category catalog with listing counts (optionally per resource type) | `GET /store/categories` |
| Browse approved Grid apps with category/rank | `GET /store/apps` |
| Marketplace listing detail | `GET /marketplace/{type}/{namespace}/{name}` |
| Draft or submit a store listing | `POST /store/listings/draft`, `POST /store/submit` |
| Tag slugs with titles and public app counts | `GET /store/tags` |
| Browse approved registry skills | `GET /store/skills` |
| Browse approved agents or flows | `GET /store/agents`, `GET /store/flows` |
| Listing count per resource type | `GET /store/counts` |
| Autocomplete / unified discovery | `GET /suggest` |
| Full-text search with scores | `GET /search` |
| Run an app | `POST /run` ([Tasks](/docs/api/rest/tasks)) |
| Read skill instructions | `GET /skills/{namespace}/{name}/content` |
| Manage your workspace's apps or skills | [Apps](/docs/api/rest/apps) / [Skills](/docs/api/rest/skills) (authenticated) |

---

## Related

| Guide | Topic |
|-------|--------|
| [Browsing the Grid](/docs/apps/browsing-grid) | App discovery in the web app |
| [Skill registry](/docs/skills/registry) | Workspace skill discovery |
| [Search concept](/docs/concepts/search) | Suggest vs search |
