Manage your apps and versions via REST.
For running apps (creating tasks), see Tasks. For CLI deploy workflows, see Deploying.
→ Apps concept · Extending Apps
Authentication
Read endpoints require the apps:read scope. Write endpoints (including setting the current version) require apps:write.
App refs and naming
Apps are identified by namespace/name — for example myteam/image-gen. This ref is what callers pass to POST /run, the CLI (belt app run namespace/name), and agent tools.
| Rule | Detail |
|---|---|
| Unique ref | Each namespace/name pair is unique among live apps. Creating or deploying to a taken ref returns 409 name_conflict. |
| Same name, different namespace | The same name slug can exist in multiple namespaces — even under the same owner (for example arxiv/search and exa/search). |
| Updates | Redeploying to an existing ref creates a new version instead of a second app — see Deploying. |
| Deleted apps | Deleting an app releases its namespace/name ref for reuse. |
Pin a specific version with @version-id (for example myteam/image-gen@ver_abc123). Omit the version pin to use the current active version.
Create or deploy app
POST /apps
Registers a new app or deploys a new version to an existing namespace/name ref. The CLI uses this endpoint for belt app deploy. Requires the apps:write scope.
Request
| Field | Type | Required | Description |
|---|---|---|---|
id | string | No | App ID — include to deploy a new version of a known app instead of matching by namespace/name |
name | string | Yes | App slug (with namespace, forms the ref) |
namespace | string | No | Ignored for everyone except platform admins: the app is always created in the caller's workspace namespace. |
title | string | No | Display title |
description | string | No | Store listing blurb — max 200 characters |
agent_description | string | No | Short text for agent tool catalogs |
category | string | No | Store category |
images | object | No | Store card, thumbnail, and banner URLs |
tags | string[] | No | Replace the app's tags. Omitted, deploy uses the version's metadata.tags, and keeps the stored tags when that is empty too |
version | object | No | App version payload (kernel, code bundle, schemas, credentials, …) |
preserve_current_version | boolean | No | When redeploying, keep the current active version (staged deploy). Default false promotes the new version. |
When namespace/name already exists, the call creates a new version on that app (same as redeploying). Omitting description on a redeploy keeps the stored listing text.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | validation_error | Invalid input — for example description longer than the listing limit. detail names the field and limit. No app row or version is created. |
| 403 | forbidden | Caller lacks write access on the app |
| 402 / 403 | limit_exceeded, feature_not_available, … | Plan entitlements block creating a new private app — see Entitlements |
CLI equivalent: belt app deploy (add --stage to set preserve_current_version).
Get App
GET /apps/{id}
Returns a single app by ID, including the active version in version_id and version.
GET /apps/{namespace}/{name}
Returns an app by namespace and name (for example myteam/my-app). The handler resolves ref routes (platform aliases): the JSON describes the target app (namespace, name, id, and version are the resolved target, which may differ from the path you requested). When the ref, or the alias it resolves through, pins a multi-function entry point (for example elevenlabs/text-to-dialogue → elevenlabs/eleven-v3:dialogue), resolved_function names it. POST /apps/run and the CLI already honour a pinned function; resolved_function lets clients open the same entry point without guessing from the target app's default.
Web app aliases: Public GET /ref-routes/site returns the marketing redirect table (redirects map: site path → site path or URL). For redirect-mode app aliases whose target ref pins a function, the destination path includes ?function=<name> (for example /apps/elevenlabs/eleven-v3?function=dialogue).
Returns 404 with code not_found when no app matches; use Search for broader discovery.
App objects include listing metadata used by the Grid, store API, and search:
| Field | Type | Description |
|---|---|---|
category | string | Grid grouping: image, video, audio, text, chat, 3d, flow, decision, or other |
tags | string[] | Lowercase slugs naming tasks and traits (for example text-to-image, open-weights). Omitted when empty. |
resolved_function | string | Present on ref lookup responses (GET /apps/{namespace}/{name} and refs that pin a function, for example myteam/dialogue:stream). Names the function the server resolved from the ref or a retired-name route. Empty means the client chooses, starting from the version's default_function. Not set on AppVersionDTO — use default_function there. |
images | object | Store card, thumbnail, and banner URLs |
title | string | Display name; falls back to name when empty |
App category
| Value | Meaning |
|---|---|
image, video, audio, text, chat, 3d, flow, other | Standard Grid groupings — see Configuration — Categories |
decision | Decision models: structured questions in, a probability distribution over answers out — no generative media output |
Decision apps use the DecisionInput / DecisionOutput JSON contract (choice, score, and noul questions keyed by id). Vision models add images; video models add videos, after the images. See Decision apps and JavaScript SDK — Decision app types.
When you change an app's category, its public store listing's Grid category follows on the next app update or deploy.
App tags
Tags name what an app does (text-to-speech, image-editing) and how it behaves (realtime, open-weights). They are indexed for search and returned on store listings.
Normalization: tags are lowercased, trimmed, deduplicated (first occurrence wins), and must match ^[a-z0-9]+(-[a-z0-9]+)*$. At most 10 tags, each at most 50 characters. Invalid tags in an API request return 400 validation.
Setting tags:
| Path | Behavior |
|---|---|
tags on POST /apps or deploy | When provided, replaces the app's stored tags |
| Omitted on deploy | Taken from the new version's metadata.tags in inf.yml (invalid entries skipped); if that is empty, existing app tags are kept |
belt app deploy --meta | Does not change tags — use a full deploy or set tags via the API |
Browseable tags are slugs that at least three public apps carry. Fetch them with GET /store/tags (Store API — List browseable tags); each entry includes slug, title, and count. The set changes as public usage changes. Any slug that matches the format above is still valid on an app, in search, and in store tag filters even when it is not returned from /store/tags (for example virtual-try-on or ocr). For slugs not in that list, capitalize hyphen-separated words client-side or show the raw slug.
List Apps
GET /apps or POST /apps/list
Returns your apps with cursor-based pagination. Requires the apps:read scope.
Request
| Field | Type | Description |
|---|---|---|
limit | number | Page size (default 50, max 100) |
cursor | string | Cursor from a previous response |
direction | string | "next" (default) or "prev" |
filters | array | { "field", "operator", "value" } — use app column names |
sort | array | { "field", "dir" } where dir is asc or desc |
search | object | { "term", "fields" } for case-insensitive substring match |
Supported filter and sort fields include namespace, name, category, visibility, status, created_at, and updated_at. Unknown filter or sort columns return 400 (Invalid sort or filter field). See Cursor pagination.
Tag filter: use field tag with operator eq and a single slug (for example text-to-image). Returns apps whose tags array includes that value, the same matching as the public store list.
1curl -X POST https://api.inference.sh/apps/list \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{5 "limit": 20,6 "filters": [{ "field": "tag", "operator": "eq", "value": "text-to-image" }]7 }'Text search: search.term is required. Each name in search.fields must be an allowlisted column (namespace, name, description, category, agent_description, and others on the app model). Invalid or empty field names are ignored; if no valid fields remain, the search clause is skipped.
Example — filter by namespace:
1curl -X POST https://api.inference.sh/apps/list \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{5 "limit": 20,6 "filters": [{ "field": "namespace", "operator": "eq", "value": "myteam" }]7 }'Example — search name and description:
1curl -X POST https://api.inference.sh/apps/list \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{5 "limit": 20,6 "search": { "term": "flux", "fields": ["name", "description"] }7 }'Response
| Field | Type | Description |
|---|---|---|
items | array | App objects |
next_cursor | string | Cursor for the next page |
prev_cursor | string | Cursor for the previous page |
has_next | boolean | More results available |
has_previous | boolean | Previous page available |
List Versions
GET /apps/{id}/versions or POST /apps/{id}/versions/list
Returns version history for an app. Each version has its own ID.
Get Version
GET /apps/{id}/versions/{versionId}
Returns a specific app version. versionId is the version's ID (the segment after @ in refs like myteam/my-app@ver_abc123).
Deploy warnings are not stored on versions: they come back only on version.warnings in the POST /apps response that registered the version. See Deploying: Warnings.
Custom UI page (ui)
An app version may name an artifact page to render in place of the generic task output — in chat, the workspace app runner, and MCP hosts that support MCP Apps.
| Field | Location | Description |
|---|---|---|
ui.artifact | Top-level on AppVersionDTO | Artifact ref or id (see below) |
metadata.ui | Version metadata map | Same { "artifact": "…" } object; ui mirrors it on read |
Artifact ref formats:
| Form | Behavior |
|---|---|
namespace/name | Uses the artifact's current published version |
namespace/name@version | Pins one published version |
art_… | Artifact id |
The publisher must be able to read the artifact when the version is created or updated; a missing or unreadable ref fails with 400 validation_error. The page must also be at least as visible as the app (a public app needs a public page, a workspace app a page the workspace can read), or the deploy is refused with the same error: whoever can read a chat that ran the app sees the page. Viewers who cannot read the artifact still run the app — they simply see the default output view instead of the custom page.
Set ui from the web app under requirements → ui page, or include metadata.ui when you register a version (deploy). Clear it by omitting metadata.ui or setting an empty artifact.
1{2 "metadata": {3 "ui": { "artifact": "myteam/run-dashboard" }4 }5}Update app metadata
Change listing fields on an app without redeploying code. Kernel and version metadata still come from deploy; use Set current version to promote a version.
Partial update (recommended)
PATCH /apps/{id}
Updates only the fields you include in the request body; all other app fields are left unchanged. Requires apps:write.
| Field | Type | Description |
|---|---|---|
title | string | Display name on the Grid and store |
description | string | Short listing description |
agent_description | string | Longer text for agent tool selection |
category | string | Grid grouping — see App category |
images | object | Store card, thumbnail, and banner URLs |
tags | string[] | Replaces the app's tag list — see App tags |
1curl -X PATCH "https://api.inference.sh/apps/app_abc123" \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{"title":"Renamed app","tags":["text-to-image","open-weights"]}'Returns the updated app (same shape as Get App).
version_id, lifecycle status, and visibility are not updated by this endpoint — use Set current version, Update status, and the visibility endpoint instead. Sending only system-managed keys (id, short_id, user_id, team_id, created_at, deleted_at) returns 400 invalid_request. Any other JSON key not in the table above returns 400 validation.
Full update
POST /apps/{id}
Replaces editable app columns from the request body. Requires apps:write. For metadata-only changes, prefer PATCH so omitted fields are not cleared.
Omitted tags on POST keep the stored tag list; an empty tags array clears tags. Like PATCH, this endpoint does not change version_id or lifecycle status — older clients that omitted those fields no longer clear the active version or take a live app offline.
Set Current Version
PUT /apps/{id}/versions/{versionId}/current
Sets which version is active for the app. The active version is what runs when callers use namespace/name without @version, and what the Grid and search index expose as the live app.
Requires the apps:write scope. No request body. Idempotent: calling it again with the same version has no effect.
Example
1curl -X PUT "https://api.inference.sh/apps/app_abc123/versions/ver_xyz789/current" \2 -H "Authorization: Bearer inf_your_key"Response
Returns the updated app (same shape as Get App), with version_id and version pointing at the promoted version.
1{2 "id": "app_abc123",3 "namespace": "myteam",4 "name": "my-app",5 "version_id": "ver_xyz789",6 "version": { "id": "ver_xyz789", "kernel": "python-3.11" }7}When to use this
- After a staged deploy (
belt app deploy --stage) — promote the new version when you are ready - To roll back — set an older version as current
- From automation — switch active versions without using the web UI
By default, belt app deploy (without --stage) makes the new version current automatically. Use staged deploys plus this endpoint when you want to test a version before promoting it.
Migration from POST /apps/{id}/current-version
The previous endpoint accepted the version ID in the JSON body:
1# Removed — do not use2curl -X POST "https://api.inference.sh/apps/app_abc123/current-version" \3 -H "Authorization: Bearer inf_your_key" \4 -H "Content-Type: application/json" \5 -d '{"version_id": "ver_xyz789"}'Use PUT /apps/{id}/versions/{versionId}/current instead, with versionId in the path.
App lifecycle status
Apps have a status field separate from visibility. Visibility controls who can see the app (public, team, private). Status controls whether new runs can be created and how the app appears in discovery.
App objects include:
| Field | Type | Description |
|---|---|---|
status | string | active, maintenance, deprecated, or retired (default active) |
status_message | string | Optional human-readable explanation shown to callers |
status_changed_at | string | ISO timestamp of the last status change |
| Status | New runs | Discovery |
|---|---|---|
active | Allowed | Listed in store and search (when public) |
deprecated | Allowed — existing integrations keep working | Listed in store and search (when public) |
maintenance | Blocked — 503 with app_maintenance | Still visible to owners; public store/search behavior unchanged |
retired | Blocked — 410 with app_retired | Excluded from store listings and search; historical tasks remain accessible |
Use deprecated when you want callers to migrate but still allow runs. Use maintenance for temporary outages. Use retired when an app should no longer be discoverable or runnable while preserving run history.
Update status
POST /apps/{id}/status
Requires the apps:write scope. The caller must have write permission on the app.
| Field | Type | Required | Description |
|---|---|---|---|
status | string | Yes | active, maintenance, deprecated, or retired |
message | string | No | Stored in status_message and shown when runs are blocked |
Returns the updated app (same shape as Get App).
1curl -X POST "https://api.inference.sh/apps/app_abc123/status" \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{"status": "deprecated", "message": "Use myteam/flux-v2 instead"}'When an app is in maintenance or retired, POST /run and POST /apps/run return errors — see Tasks API — Run Task errors.