API: Store
Halo HTTP API for the Store: browse and search listings, install and uninstall Digital Humans and skills, and manage Store sources and updates.
Browse and search the Store, read a listing, install and uninstall Digital Humans and skills, install from a local .dhpkg file, and manage Store sources and updates. Publishing happens in the Halo app; see Publish to the Store.
Paths are relative to your Halo base URL, and every request carries the access password. Set $HALO_URL and $HALO_TOKEN as in the quickstart; base URL, authentication and error conventions are in the HTTP API overview.
Preview the slash-command name a skill title would install under
GET /api/skills/command-name Assistant
- Query: ?name=. A missing name returns an empty string, not a 400. Pure string transform — it does not check whether that name is already taken.
Query parameters: name
Response
{success:true,data:string} // e.g. "My Skill" -> "my-skill"Example
curl -s "$HALO_URL/api/skills/command-name" \
-H "Authorization: Bearer $HALO_TOKEN"Get a listing's SKILL.md/README document
GET /api/store/app-document Assistant
- slug is a query param (?slug=...), not a path segment — scoped slugs like "owner/repo" contain "/". content is null when the source has no document.
Query parameters: slug
Response
{success:true,data:{content:string|null}}Example
curl -s "$HALO_URL/api/store/app-document" \
-H "Authorization: Bearer $HALO_TOKEN"List store listings (legacy, non-paginated; prefer POST /api/store/query)
GET /api/store/apps Assistant
- Query params: search, locale, category, type, tags (comma-separated).
Query parameters: category, locale, search, tags, type
Response
{success:true,data:[{slug,name,version,author,description,type,category,tags,...}]}Example
curl -s "$HALO_URL/api/store/apps" \
-H "Authorization: Bearer $HALO_TOKEN"Get full detail for one store listing by slug
GET /api/store/apps/:slug Assistant
Path parameters
| Name | Description |
|---|---|
slug | Store listing slug, from GET /api/store/apps. |
Response
{success:true,data:{entry:{slug,name,version,...},spec:<AppSpec>,registryId}} or {success:false,error}Example
curl -s "$HALO_URL/api/store/apps/<slug>" \
-H "Authorization: Bearer $HALO_TOKEN"Install an app from the store by slug (REST-style path variant)
POST /api/store/apps/:slug/install Reversible Assistant
- Always send
spaceId. It is used exactly as sent: leave it out and the item installs for every workspace; name another workspace and it installs there. Both answersuccess: true, and the response does not say where it went. - Send
"spaceId": nullonly for a deliberate global install of a skill or MCP app. Optional field:userConfig(object). - Same as
POST /api/store/installwith the slug in the path, including the 403MCP_COMMAND_BLOCKEDcase.
Path parameters
| Name | Description |
|---|---|
slug | Store listing slug, from GET /api/store/apps. |
Request body
{
"spaceId": "<spaceId>"
}Response
{success:true,data:{appId}} or {success:false,error,code?}Example
curl -sX POST "$HALO_URL/api/store/apps/<slug>/install" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"spaceId":"<spaceId>"}'Get which store features this build supports (publish, reviews, install counts)
GET /api/store/capabilities Assistant
- Reports which Store features this build supports. Publishing happens in the Halo app, not through this API.
Response
{success:true,data:{catalog:true,installs:boolean,publish:boolean,reviewWorkflow:boolean,identity:"none"|string}}Example
curl -s "$HALO_URL/api/store/capabilities" \
-H "Authorization: Bearer $HALO_TOKEN"Get the store category enum used for browsing and publishing
GET /api/store/category-taxonomy Assistant
- Categories are keyed per app type (skill/automation/mcp/extension) with "default" as the fallback list for types without their own entry.
Response
{success:true,data:{default:[{id,label|labelKey,icon?}],<appType>?:[...]}}Example
curl -s "$HALO_URL/api/store/category-taxonomy" \
-H "Authorization: Bearer $HALO_TOKEN"Get the curated store home page (featured/trending sections)
GET /api/store/discover-page Assistant
- Query params: locale?, pageSize?. Falls back to a single paginated catalog node when no curated layout is configured.
Query parameters: locale, pageSize
Response
{success:true,data:{version,nodes:[{type,title?,subtitle?,entries?:[RegistryEntry],collections?,hasMore?}]}}Example
curl -s "$HALO_URL/api/store/discover-page" \
-H "Authorization: Bearer $HALO_TOKEN"Dismiss an available update so it stops appearing as pending
POST /api/store/ignore-version Reversible Assistant
- Only hides that one version; a newer update still surfaces normally.
Request body
{
"appId": "<appId — a uuid from GET /api/apps>",
"version": "1.2.0"
}Response
{success:true,data:null}Example
curl -sX POST "$HALO_URL/api/store/ignore-version" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"appId":"<appId — a uuid from GET /api/apps>","version":"1.2.0"}'Install a digital human from a local .dhpkg file
POST /api/store/import-dhpkg Reversible Assistant
- Installs and activates the package's Digital Human; it then runs on its own schedule. Undo with
DELETE /api/apps/:appId. - Unlike installing in the Halo app or through its assistant, this does not fetch the package's required skills or undo a half-finished install. Check
GET /api/apps/:appIdafterwards; an activation that fails after the install still answerssuccess: true. filePathis a path on the computer running Halo.spaceIdis used exactly as sent: left out means an install for every workspace, another workspace installs there, and neither is reported back.
Request body
{
"filePath": "/absolute/path/to/app.dhpkg",
"spaceId": "<spaceId>"
}Response
{success:true,data:{appId}}Errors
- 400 when
filePathis missing.
Example
curl -sX POST "$HALO_URL/api/store/import-dhpkg" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"filePath":"/absolute/path/to/app.dhpkg","spaceId":"<spaceId>"}'Install an app from the store by slug
POST /api/store/install Reversible Assistant
- Always send
spaceId. It is used exactly as sent: leave it out and the item installs for every workspace; name another workspace and it installs there. Both answersuccess: true, and the response does not say where it went. - Send
"spaceId": nullonly for a deliberate global install of a skill or MCP app. Optional field:userConfig(object). - Same operation as
POST /api/store/apps/:slug/install, with the slug in the body.
Request body
{
"slug": "author/app-id",
"spaceId": "<spaceId>"
}Response
{success:true,data:{appId}} or {success:false,error,code?}Errors
- 400 = missing slug.
- 403 with
code: "MCP_COMMAND_BLOCKED"when the item is an MCP server this build's policy blocks.
Example
curl -sX POST "$HALO_URL/api/store/install" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"slug":"author/app-id","spaceId":"<spaceId>"}'List the signed-in creator's own published apps
GET /api/store/my-publications Assistant
- Fails when no creator identity is signed in — this build may not support publishing at all (check
GET /api/store/capabilitiesfirst).
Response
{success:true,data:[{slug,name?,displayName?,type?,version,status:"listed"|"hidden"|"rejected",takedownBy?,rejectReason?,submittedAt?,channel?,installs?}]}Example
curl -s "$HALO_URL/api/store/my-publications" \
-H "Authorization: Bearer $HALO_TOKEN"Find the installed app that a store slug would resolve to
POST /api/store/publish/find-app Assistant
- Optional fields: type, author. appId is null when no locally installed app uniquely matches.
Request body
{
"slug": "author/app-id"
}Response
{success:true,data:{appId: string|null}}Example
curl -sX POST "$HALO_URL/api/store/publish/find-app" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"slug":"author/app-id"}'Preview the slug and version a publish of an app would produce
POST /api/store/publish/preview Assistant
- Optional fields: author, name (override the store name before deriving the slug). storeVersion is null when the slug has never been published.
Request body
{
"appId": "<appId — a uuid from GET /api/apps>"
}Response
{success:true,data:{slug,localVersion,storeVersion:string|null}}Example
curl -sX POST "$HALO_URL/api/store/publish/preview" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"appId":"<appId — a uuid from GET /api/apps>"}'Paginated search over store listings (primary browse endpoint)
POST /api/store/query Assistant
- All fields optional: search, type, category, page, pageSize, locale.
Request body
{
"search": "weather",
"page": 1,
"pageSize": 20
}Response
{success:true,data:{items:[{slug,name,version,author,description,type,category,tags,...}],total?,hasMore,groups?:[{type,count,hasMore}],sources:[{registryId,status,error?}]}}Example
curl -sX POST "$HALO_URL/api/store/query" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"search":"weather","page":1,"pageSize":20}'Force a full re-download of the store registry index
POST /api/store/refresh Assistant
- Heavier than
POST /api/store/revalidate— use revalidate for a cheap freshness check.
Response
{success:true,data:undefined}Example
curl -sX POST "$HALO_URL/api/store/refresh" \
-H "Authorization: Bearer $HALO_TOKEN"List configured store registry sources
GET /api/store/registries Assistant
adapterConfigis returned as stored and can hold an API key in plain text. Treat the response like a secret.
Response
{success:true,data:[{id,name,url,enabled,isDefault?,sourceType?,adapterConfig?}]}Example
curl -s "$HALO_URL/api/store/registries" \
-H "Authorization: Bearer $HALO_TOKEN"Add a new registry source
POST /api/store/registries Assistant
- Optional fields:
sourceType(string) andadapterConfig(object, adapter-specific, for example an API key). The response returnsadapterConfigas stored. urlmust be http(s) and must not point to a loopback, private or link-local host. Fails if a source with the same URL already exists.
Request body
{
"name": "My Registry",
"url": "https://example.com/registry"
}Response
{success:true,data:{id,name,url,enabled,isDefault:false,sourceType?,adapterConfig?}} | {success:false,error}Example
curl -sX POST "$HALO_URL/api/store/registries" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"My Registry","url":"https://example.com/registry"}'Remove a configured registry source
DELETE /api/store/registries/:registryId Reversible Assistant
- Fails with an error (not thrown) when the target is the built-in/default registry — those cannot be removed. Can be added back with
POST /api/store/registries.
Path parameters
| Name | Description |
|---|---|
registryId | Store source id, from GET /api/store/registries. |
Response
{success:true,data:undefined} or {success:false,error}Example
curl -sX DELETE "$HALO_URL/api/store/registries/<registryId>" \
-H "Authorization: Bearer $HALO_TOKEN"Update a registry source's adapter config (e.g. its API key)
PATCH /api/store/registries/:registryId/adapter-config Reversible Assistant
- Body is a flat JSON object of adapter-specific fields — exact keys depend on the registry's sourceType.
- Write-only: the response never echoes the config back, so this is safe for setting credentials. 400 if the body is missing or not a plain object.
Path parameters
| Name | Description |
|---|---|
registryId | Store source id, from GET /api/store/registries. |
Request body
{
"apiKey": "sk-..."
}Response
{success:true,data:undefined}Example
curl -sX PATCH "$HALO_URL/api/store/registries/<registryId>/adapter-config" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"apiKey":"sk-..."}'Enable or disable a configured registry source
POST /api/store/registries/:registryId/toggle Reversible Assistant
Path parameters
| Name | Description |
|---|---|
registryId | Store source id, from GET /api/store/registries. |
Request body
{
"enabled": true
}Response
{success:true,data:undefined}Errors
- 400 if enabled is missing or not a boolean.
Example
curl -sX POST "$HALO_URL/api/store/registries/<registryId>/toggle" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled":true}'Cheap conditional freshness check for the store index
POST /api/store/revalidate Assistant
- changed:false means the index did not move (a 304-equivalent); the browse list does not need refetching.
Response
{success:true,data:{changed:boolean}}Example
curl -sX POST "$HALO_URL/api/store/revalidate" \
-H "Authorization: Bearer $HALO_TOKEN"Take down one of the creator's own published apps
POST /api/store/unpublish Reversible Assistant
- Can be republished later; already-installed/downloaded copies elsewhere are not affected either way.
Request body
{
"slug": "author/app-id"
}Response
{success:true,data:null}Example
curl -sX POST "$HALO_URL/api/store/unpublish" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"slug":"author/app-id"}'List available updates for installed apps
GET /api/store/updates Assistant
Response
{success:true,data:[{appId,currentVersion,latestVersion,entry,strategy,severity}]}Example
curl -s "$HALO_URL/api/store/updates" \
-H "Authorization: Bearer $HALO_TOKEN"Apply an available update to an installed app
POST /api/store/updates/:appId/apply Reversible Assistant
- mode is optional: "patch_minor"|"major"|"force", defaults to "force". No built-in downgrade path — reinstalling the old version is the way back.
- A digital human keeps the user's version of every field that differs from the author's new version (prompt, run schedule, name, ...); kept lists those fields, empty when the update applied in full. editsKnown false means there was no earlier author version to compare with, so every difference was kept. The user can switch kept fields to the author's version in the app.
Path parameters
| Name | Description |
|---|---|
appId | Digital Human id, from GET /api/apps. |
Request body
{
"mode": "force"
}Response
{success:true,data:{appId,from,to,severity,kept,editsKnown}} or {success:false,error}Example
curl -sX POST "$HALO_URL/api/store/updates/<appId>/apply" \
-H "Authorization: Bearer $HALO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mode":"force"}'Re-check all installed apps for available updates right now
POST /api/store/updates/check-now Reversible Assistant
- Apps set to auto-update silently apply any patch/minor update found — this is not just a read.
Response
{success:true,data:[{appId,currentVersion,latestVersion,entry,strategy,severity}]}Example
curl -sX POST "$HALO_URL/api/store/updates/check-now" \
-H "Authorization: Bearer $HALO_TOKEN"