Hoody Run: Resolve
Section titled “Hoody Run: Resolve”Hoody Run is a pure application resolver. Given an app name plus selector hints, it returns the exact shell command needed to launch that app, without ever executing it. The CLI equivalent is hoody run <app>.
A resolve response is one of two shapes: status: "dry-run" (a single candidate is selected and its shell_command is returned, with an optional preview browser handoff) or status: "resolved" (a full ranked candidate list is returned, with no single candidate selected). Every candidate carries a kind of gui, cli, or any (any means the source did not classify the candidate).
Resolve an application
Section titled “Resolve an application”Map an app name plus selector hints to the exact shell command to launch it. Both the query and JSON body forms below accept the full selector and return the same response shape.
GET /api/v1/run/resolve
Section titled “GET /api/v1/run/resolve”Resolve and select an application using query parameters, then return the exact shell command to run. Supports all selector fields plus pick mode and output control.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
app | query | string | Yes | Primary name query |
os | query | string | No | Target OS filter |
source | query | array | No | Source kind filter (repeatable) |
kind | query | string | No | App kind filter |
arch | query | string | No | Target CPU architecture filter |
tags | query | array | No | Free-form selector tags (repeatable). Accepted and passed through to sources and recipe templates; they do not filter or rank candidates |
profile | query | string | No | Named profile for default preferences |
channel | query | string | No | Release channel hint |
version | query | string | No | Exact version or provider-defined version constraint |
variant | query | string | No | Provider-specific variant hint |
publisher | query | string | No | Publisher hint for curated registries |
repo | query | string | No | Repository hint such as owner/name |
release | query | string | No | Release hint such as a tag name |
asset | query | string | No | Desired asset name or pattern |
pick | query | string | No | Candidate selection mode (ask, first, index, id) |
pick_index | query | integer | No | Candidate index (required when pick=index) |
candidate_id | query | string | No | Specific candidate ID (required when pick=id) |
set_id | query | string | No | Bind pick to a specific candidate set |
terminal_id | query | integer | No | Terminal session ID. Default: 1 |
display | query | string | No | X11 DISPLAY number |
origin | query | string | No | Origin identifier for observability propagation |
dry_run | query | boolean | No | If true, force command-only response (hoody-run never executes) |
print_curl | query | string | No | Generate curl command (hoody-run) |
format | query | string | No | Output format (json or html) |
limit | query | integer | No | Max candidates. Default: 25 |
Response
Section titled “Response”curl "https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/resolve?app=firefox&kind=gui&os=linux&pick=first" \ -H "Authorization: Bearer <token>"{ "status": "dry-run", "set_id": "a1b2c3d4e5f6", "selected": { "candidate_id": "nix-firefox-128", "title": "Firefox (nixpkgs)", "description": "Mozilla Firefox web browser via nixpkgs", "kind": "gui", "version": "128.0.3", "homepage": "https://www.mozilla.org/firefox/", "tags": ["browser", "web"], "provider": "nix", "source_id": "nixpkgs", "score": 95, "reasons": ["exact name match", "high priority source"], "run_plan": { "command": "nix run nixpkgs#firefox" }, "shell_command": "nix run nixpkgs#firefox" }, "shell_command": "nix run nixpkgs#firefox", "handoff": { "state": "preview", "terminal_id": 2, "display": ":2", "preview_display_url": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-display-2.node-example-1.containers.hoody.com/", "preview_terminal_url": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-terminal-2.node-example-1.containers.hoody.com/" }, "warnings": []}{ "error": "missing app", "code": "MISSING_APP", "status": 400}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_APP | Missing app query | No app name was provided in the request | Set app to the desired program name |
INVALID_SELECTOR | Invalid selector parameter | One or more selector parameters could not be parsed | Check enum values and numeric fields, then retry |
UNKNOWN_PROFILE | Unknown profile | The requested profile does not exist | Choose a profile returned by listProfiles or getConfig |
INVALID_PICK | Invalid pick request | The pick mode requirements were not satisfied or the selected candidate was not found | Search first, then supply a valid pick_index or candidate_id |
NO_CANDIDATES | No candidates | A pick was requested but the search found no candidate to select | Widen the selector (source, os, kind) or check source health |
{ "error": "candidate denied by policy", "code": "POLICY_DENIED", "status": 403}| Error Code | Title | Description | Resolution |
|---|---|---|---|
POLICY_DENIED | Candidate denied by policy | A pick from an older cached set resolved to a candidate that the current effective policy forbids | Pick a candidate allowed by the active profile, or search again under the current policy |
{ "error": "candidate set expired", "code": "SET_EXPIRED", "status": 409}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SET_EXPIRED | Candidate set expired | The set_id-bound candidate set has expired; pick=index can no longer be resolved race-safely | Re-run search to obtain a fresh set_id, then retry the pick |
{ "error": "internal error", "code": "INTERNAL_ERROR", "status": 500}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INTERNAL_ERROR | Internal error | The service failed while handling the request | Retry; if it persists, check the service logs |
{ "error": "source resolution failed", "code": "SOURCE_RESOLUTION_FAILED", "status": 502}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SOURCE_RESOLUTION_FAILED | Source resolution failed | Candidate resolution could not be completed because upstream source work failed | Retry or inspect provider/source health |
POST /api/v1/run/resolve
Section titled “POST /api/v1/run/resolve”Same behavior as GET /api/v1/run/resolve, but accepts the full selector as a JSON request body. Useful for programmatic clients and complex selectors.
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
app | string | Yes | Primary name query (aliases q, name) |
os | string | No | Target OS filter |
kind | string | No | App kind filter |
source | array | No | Source kind filter (repeatable). Empty means no filter |
arch | string | No | Target CPU architecture filter |
tags | array | No | Free-form selector tags (repeatable). Accepted and passed through to sources and recipe templates; they do not filter or rank candidates |
profile | string | No | Named profile for default preferences |
channel | string | No | Release channel hint (for example stable or beta) |
version | string | No | Exact version or provider-defined version constraint |
variant | string | No | Provider-defined variant hint |
publisher | string | No | Publisher hint for curated registries |
repo | string | No | Repository hint such as owner/name |
release | string | No | Release hint such as a tag name |
asset | string | No | Desired asset name or pattern |
pick | string | No | Candidate selection mode (ask, first, index, id) |
pick_index | integer | No | Candidate index (required when pick=index) |
candidate_id | string | No | Specific candidate ID (required when pick=id) |
set_id | string | No | Bind pick to a specific candidate set |
terminal_id | integer | No | Terminal session ID. Default: 1 |
display | string | No | X11 DISPLAY number |
origin | string | No | Origin identifier for observability propagation |
format | string | No | Output format (json or html) |
dry_run | boolean | No | Force command-only output. hoody-run never executes |
print_curl | string | No | Generate curl command |
limit | integer | No | Max candidates. Default: 25 |
Response
Section titled “Response”curl -X POST "https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/resolve" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "app": "firefox", "os": "linux", "kind": "gui", "pick": "first" }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.run.resolve({ app: 'firefox', os: 'linux', kind: 'gui', pick: 'first' });{ "status": "dry-run", "set_id": "a1b2c3d4e5f6", "selected": { "candidate_id": "nix-firefox-128", "title": "Firefox (nixpkgs)", "description": "Mozilla Firefox web browser via nixpkgs", "kind": "gui", "version": "128.0.3", "provider": "nix", "source_id": "nixpkgs", "score": 95, "run_plan": { "command": "nix run nixpkgs#firefox" }, "shell_command": "nix run nixpkgs#firefox" }, "shell_command": "nix run nixpkgs#firefox", "handoff": { "state": "preview", "terminal_id": 2, "display": ":2", "preview_display_url": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-display-2.node-example-1.containers.hoody.com/", "preview_terminal_url": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-terminal-2.node-example-1.containers.hoody.com/" }, "warnings": []}{ "error": "missing app", "code": "MISSING_APP", "status": 400}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_APP | Missing app query | No app name was provided in the request body | Set app to the desired program name |
INVALID_SELECTOR | Invalid selector parameter | One or more selector fields could not be parsed | Check enum values and numeric fields, then retry |
UNKNOWN_PROFILE | Unknown profile | The requested profile does not exist | Choose a profile returned by listProfiles or getConfig |
INVALID_PICK | Invalid pick request | The pick mode requirements were not satisfied or the selected candidate was not found | Search first, then supply a valid pick_index or candidate_id |
NO_CANDIDATES | No candidates | A pick was requested but the search found no candidate to select | Widen the selector (source, os, kind) or check source health |
{ "error": "candidate denied by policy", "code": "POLICY_DENIED", "status": 403}| Error Code | Title | Description | Resolution |
|---|---|---|---|
POLICY_DENIED | Candidate denied by policy | A pick from an older cached set resolved to a candidate that the current effective policy forbids | Pick a candidate allowed by the active profile, or search again under the current policy |
{ "error": "candidate set expired", "code": "SET_EXPIRED", "status": 409}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SET_EXPIRED | Candidate set expired | The set_id-bound candidate set has expired; pick=index can no longer be resolved race-safely | Re-run search to obtain a fresh set_id, then retry the pick |
{ "error": "internal error", "code": "INTERNAL_ERROR", "status": 500}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INTERNAL_ERROR | Internal error | The service failed while handling the request | Retry; if it persists, check the service logs |
{ "error": "source resolution failed", "code": "SOURCE_RESOLUTION_FAILED", "status": 502}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SOURCE_RESOLUTION_FAILED | Source resolution failed | Candidate resolution could not be completed because upstream source work failed | Retry or inspect provider/source health |
Path-based resolve
Section titled “Path-based resolve”Use these endpoints when you want clean, bookmarkable URLs that embed the app selector in the path itself. They accept the same query-string overrides as the resolve endpoints, and the terminal-anchored form lets you pin a target terminal session in the URL.
GET /api/v1/run/go/{rest}
Section titled “GET /api/v1/run/go/{rest}”Resolve an application using path segments. Both positional and key-value forms are supported:
- Positional:
/api/v1/run/go/{app},/api/v1/run/go/{os}/{app},/api/v1/run/go/{os}/{source}/{app},/api/v1/run/go/{os}/{source}/{kind}/{app} - Key-value:
/api/v1/run/go/app/{app}/os/{os}/source/{source}/kind/{kind}/pick/{pick}/...
{rest} captures everything after /go/.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
rest | path | string | Yes | Path segments for positional or key-value app specification |
os | query | string | No | Target OS filter when not supplied in the path |
source | query | array | No | Source kind filter (repeatable) |
kind | query | string | No | App kind filter when not supplied in the path |
arch | query | string | No | Target CPU architecture filter |
tags | query | array | No | Free-form selector tags (repeatable). Accepted and passed through to sources and recipe templates; they do not filter or rank candidates |
profile | query | string | No | Named profile for default preferences |
channel | query | string | No | Release channel hint |
version | query | string | No | Exact version or provider-defined version constraint |
variant | query | string | No | Provider-specific variant hint |
publisher | query | string | No | Publisher hint for curated registries |
repo | query | string | No | Repository hint such as owner/name |
release | query | string | No | Release hint such as a tag name |
asset | query | string | No | Desired asset name or pattern |
pick | query | string | No | Candidate selection mode (ask, first, index, id) |
pick_index | query | integer | No | Candidate index (required when pick=index) |
candidate_id | query | string | No | Specific candidate ID (required when pick=id) |
set_id | query | string | No | Bind pick to a specific candidate set |
terminal_id | query | integer | No | Terminal session ID when not supplied in the path |
display | query | string | No | X11 DISPLAY number |
origin | query | string | No | Origin identifier for observability propagation |
dry_run | query | boolean | No | If true, force command-only response (hoody-run never executes) |
print_curl | query | string | No | Generate curl command (hoody-run) |
format | query | string | No | Output format (json or html) |
limit | query | integer | No | Max candidates. Default: 25 |
Response
Section titled “Response”curl "https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/go/linux/nix/firefox?pick=first" \ -H "Authorization: Bearer <token>"{ "status": "dry-run", "set_id": "a1b2c3d4e5f6", "selected": { "candidate_id": "nix-firefox-128", "title": "Firefox (nixpkgs)", "description": "Mozilla Firefox web browser via nixpkgs", "kind": "gui", "version": "128.0.3", "provider": "nix", "source_id": "nixpkgs", "score": 95, "run_plan": { "command": "nix run nixpkgs#firefox" }, "shell_command": "nix run nixpkgs#firefox" }, "shell_command": "nix run nixpkgs#firefox", "handoff": { "state": "preview", "terminal_id": 2, "display": ":2", "preview_display_url": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-display-2.node-example-1.containers.hoody.com/", "preview_terminal_url": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-terminal-2.node-example-1.containers.hoody.com/" }, "warnings": []}{ "error": "missing app", "code": "MISSING_APP", "status": 400}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_APP | Missing app query | No app name was provided in the request | Set the app in the path or query string |
INVALID_SELECTOR | Invalid selector parameter | One or more selector parameters could not be parsed | Check enum values and numeric fields, then retry |
UNKNOWN_PROFILE | Unknown profile | The requested profile does not exist | Choose a profile returned by listProfiles or getConfig |
INVALID_PICK | Invalid pick request | The pick mode requirements were not satisfied or the selected candidate was not found | Search first, then supply a valid pick_index or candidate_id |
NO_CANDIDATES | No candidates | A pick was requested but the search found no candidate to select | Widen the selector (source, os, kind) or check source health |
{ "error": "candidate denied by policy", "code": "POLICY_DENIED", "status": 403}| Error Code | Title | Description | Resolution |
|---|---|---|---|
POLICY_DENIED | Candidate denied by policy | A pick from an older cached set resolved to a candidate that the current effective policy forbids | Pick a candidate allowed by the active profile, or search again under the current policy |
{ "error": "candidate set expired", "code": "SET_EXPIRED", "status": 409}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SET_EXPIRED | Candidate set expired | The set_id-bound candidate set has expired; pick=index can no longer be resolved race-safely | Re-run search to obtain a fresh set_id, then retry the pick |
{ "error": "internal error", "code": "INTERNAL_ERROR", "status": 500}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INTERNAL_ERROR | Internal error | The service failed while handling the request | Retry; if it persists, check the service logs |
{ "error": "source resolution failed", "code": "SOURCE_RESOLUTION_FAILED", "status": 502}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SOURCE_RESOLUTION_FAILED | Source resolution failed | Candidate resolution could not be completed because upstream source work failed | Retry or inspect provider/source health |
GET /api/v1/run/t/{terminal_id}/go/{rest}
Section titled “GET /api/v1/run/t/{terminal_id}/go/{rest}”Same as GET /api/v1/run/go/{rest} but with terminal_id extracted from the path prefix. Allows clean URLs that specify both the target terminal and the application in a single path. For example, /api/v1/run/t/2/go/linux/nix/firefox resolves Firefox for terminal 2.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
terminal_id | path | integer | Yes | Terminal session ID (1-65535) |
rest | path | string | Yes | Path segments for app specification |
os | query | string | No | Target OS filter when not supplied in the path |
source | query | array | No | Source kind filter (repeatable) |
kind | query | string | No | App kind filter when not supplied in the path |
arch | query | string | No | Target CPU architecture filter |
tags | query | array | No | Free-form selector tags (repeatable). Accepted and passed through to sources and recipe templates; they do not filter or rank candidates |
profile | query | string | No | Named profile for default preferences |
channel | query | string | No | Release channel hint |
version | query | string | No | Exact version or provider-defined version constraint |
variant | query | string | No | Provider-specific variant hint |
publisher | query | string | No | Publisher hint for curated registries |
repo | query | string | No | Repository hint such as owner/name |
release | query | string | No | Release hint such as a tag name |
asset | query | string | No | Desired asset name or pattern |
pick | query | string | No | Candidate selection mode (ask, first, index, id) |
pick_index | query | integer | No | Candidate index (required when pick=index) |
candidate_id | query | string | No | Specific candidate ID (required when pick=id) |
set_id | query | string | No | Bind pick to a specific candidate set |
display | query | string | No | X11 DISPLAY number |
origin | query | string | No | Origin identifier for observability propagation |
dry_run | query | boolean | No | If true, force command-only response (hoody-run never executes) |
print_curl | query | string | No | Generate curl command (hoody-run) |
format | query | string | No | Output format (json or html) |
limit | query | integer | No | Max candidates. Default: 25 |
Response
Section titled “Response”curl "https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/t/2/go/linux/nix/firefox?pick=first" \ -H "Authorization: Bearer <token>"{ "status": "dry-run", "set_id": "a1b2c3d4e5f6", "selected": { "candidate_id": "nix-firefox-128", "title": "Firefox (nixpkgs)", "description": "Mozilla Firefox web browser via nixpkgs", "kind": "gui", "version": "128.0.3", "provider": "nix", "source_id": "nixpkgs", "score": 95, "run_plan": { "command": "nix run nixpkgs#firefox" }, "shell_command": "nix run nixpkgs#firefox" }, "shell_command": "nix run nixpkgs#firefox", "handoff": { "state": "preview", "terminal_id": 2, "display": ":2", "preview_display_url": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-display-2.node-example-1.containers.hoody.com/", "preview_terminal_url": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-terminal-2.node-example-1.containers.hoody.com/" }, "warnings": []}{ "error": "missing app", "code": "MISSING_APP", "status": 400}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_APP | Missing app query | No app name was provided in the request | Set the app in the path or query string |
INVALID_SELECTOR | Invalid selector parameter | One or more selector parameters could not be parsed | Check enum values and numeric fields, then retry |
UNKNOWN_PROFILE | Unknown profile | The requested profile does not exist | Choose a profile returned by listProfiles or getConfig |
INVALID_PICK | Invalid pick request | The pick mode requirements were not satisfied or the selected candidate was not found | Search first, then supply a valid pick_index or candidate_id |
INVALID_TERMINAL_ID | Invalid terminal_id in the path | The {terminal_id} path segment is not an integer from 1 to 65535 | Use a terminal number such as /api/v1/run/t/2/go/firefox |
NO_CANDIDATES | No candidates | A pick was requested but the search found no candidate to select | Widen the selector (source, os, kind) or check source health |
{ "error": "candidate denied by policy", "code": "POLICY_DENIED", "status": 403}| Error Code | Title | Description | Resolution |
|---|---|---|---|
POLICY_DENIED | Candidate denied by policy | A pick from an older cached set resolved to a candidate that the current effective policy forbids | Pick a candidate allowed by the active profile, or search again under the current policy |
{ "error": "candidate set expired", "code": "SET_EXPIRED", "status": 409}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SET_EXPIRED | Candidate set expired | The set_id-bound candidate set has expired; pick=index can no longer be resolved race-safely | Re-run search to obtain a fresh set_id, then retry the pick |
{ "error": "internal error", "code": "INTERNAL_ERROR", "status": 500}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INTERNAL_ERROR | Internal error | The service failed while handling the request | Retry; if it persists, check the service logs |
{ "error": "source resolution failed", "code": "SOURCE_RESOLUTION_FAILED", "status": 502}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SOURCE_RESOLUTION_FAILED | Source resolution failed | Candidate resolution could not be completed because upstream source work failed | Retry or inspect provider/source health |
Search candidates
Section titled “Search candidates”List candidates for an app without picking one. Search results carry a set_id that can be passed to a subsequent resolve request to select a specific candidate race-free. The paged variant exposes a stable cursor for browsing large result sets.
GET /api/v1/run/search
Section titled “GET /api/v1/run/search”Search for runnable application candidates across all configured and enabled package sources. Returns a ranked list of candidates with stable ordering for pick-by-index operations.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
app | query | string | Yes | Primary name query (aliases q, name) |
os | query | string | No | Target OS filter |
source | query | array | No | Source kind filter (repeatable) |
kind | query | string | No | App kind filter (gui, cli, any) |
arch | query | string | No | Target CPU architecture filter |
tags | query | array | No | Free-form selector tags (repeatable). Accepted and passed through to sources and recipe templates; they do not filter or rank candidates |
profile | query | string | No | Named profile for default preferences |
channel | query | string | No | Release channel hint (for example stable or beta) |
version | query | string | No | Exact version or provider-defined version constraint |
variant | query | string | No | Provider-specific variant hint (for example portable or headless) |
publisher | query | string | No | Publisher hint for curated registries |
repo | query | string | No | Repository hint such as owner/name |
release | query | string | No | Release hint such as a tag name |
asset | query | string | No | Desired asset name or pattern |
limit | query | integer | No | Max candidates to return. Default: 25 |
Response
Section titled “Response”curl "https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/search?app=firefox&kind=gui&os=linux" \ -H "Authorization: Bearer <token>"{ "set_id": "a1b2c3d4e5f6", "candidates": [ { "candidate_id": "nix-firefox-128", "title": "Firefox (nixpkgs)", "description": "Mozilla Firefox web browser via nixpkgs", "kind": "gui", "version": "128.0.3", "provider": "nix", "source_id": "nixpkgs", "score": 95, "tags": ["browser", "web"], "run_plan": { "command": "nix run nixpkgs#firefox" } }, { "candidate_id": "pkgx-firefox-128", "title": "Firefox (pkgx)", "description": "Mozilla Firefox web browser via pkgx", "kind": "gui", "version": "128.0.3", "provider": "pkgx", "source_id": "pkgx-firefox", "score": 80, "tags": ["browser", "web"], "run_plan": { "command": "pkgx firefox" } } ]}{ "error": "missing app", "code": "MISSING_APP", "status": 400}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_APP | Missing app query | No app name was provided in the request | Set app, q, or name to the desired program |
INVALID_SELECTOR | Invalid selector parameter | One or more selector parameters could not be parsed | Check enum values and numeric fields, then retry |
UNKNOWN_PROFILE | Unknown profile | The requested profile does not exist | Choose a profile returned by listProfiles or getConfig |
{ "error": "source resolution failed", "code": "SOURCE_RESOLUTION_FAILED", "status": 502}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SOURCE_RESOLUTION_FAILED | Source resolution failed | Candidate resolution could not be completed because upstream source work failed | Retry or inspect provider/source health |
POST /api/v1/run/search/paged
Section titled “POST /api/v1/run/search/paged”Resolve a full ranked candidate set under a bounded cap, then page through it with an opaque cursor. This is the stable pagination contract for large result sets.
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
selector | object | Yes | Search selector (a full Selector object) |
cursor | string | No | Opaque pagination cursor returned by a previous paged response |
page_size | integer | No | Page size (1-100) |
The embedded selector accepts the same fields as the JSON body of POST /api/v1/run/resolve (see the request body table there).
Response
Section titled “Response”curl -X POST "https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/search/paged" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "selector": { "app": "firefox", "kind": "gui", "os": "linux" }, "page_size": 25 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.run.searchIterator({ selector: { app: 'firefox', kind: 'gui', os: 'linux' }, page_size: 25 })) { console.log(item);}{ "set_id": "a1b2c3d4e5f6", "total_count": 42, "items": [ { "candidate_id": "nix-firefox-128", "title": "Firefox (nixpkgs)", "description": "Mozilla Firefox web browser via nixpkgs", "kind": "gui", "version": "128.0.3", "provider": "nix", "source_id": "nixpkgs", "score": 95, "tags": ["browser", "web"], "run_plan": { "command": "nix run nixpkgs#firefox" } } ], "next_cursor": "eyJjIjpbIm5peC1maXJlZm94LTEyOCJdfQ"}{ "error": "invalid selector", "code": "INVALID_SELECTOR", "status": 400}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_APP | Missing app query | No app name was provided in the request | Set app to the desired program name |
INVALID_SELECTOR | Invalid selector parameter | One or more selector fields could not be parsed | Check enum values and numeric fields, then retry |
UNKNOWN_PROFILE | Unknown profile | The requested profile does not exist | Choose a profile returned by listProfiles or getConfig |
INVALID_CURSOR | Invalid cursor | The cursor is malformed, fails its signature check, was issued for a different selector, or points past the end of the set | Restart pagination from the first page with the same selector |
{ "error": "cursor set expired", "code": "CURSOR_SET_EXPIRED", "status": 409}| Error Code | Title | Description | Resolution |
|---|---|---|---|
CURSOR_SET_EXPIRED | Cursor set expired | The cached candidate set referenced by the cursor is no longer available | Restart pagination from the first page |
{ "error": "source resolution failed", "code": "SOURCE_RESOLUTION_FAILED", "status": 502}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SOURCE_RESOLUTION_FAILED | Source resolution failed | Candidate resolution could not be completed because upstream source work failed | Retry or inspect provider/source health |
Preflight
Section titled “Preflight”Resolve and normalize the execution plan for a selector without scheduling execution. The response reports the recommended execution mode, any missing requirements, and the effective policy that will be applied.
POST /api/v1/run/preflight
Section titled “POST /api/v1/run/preflight”Preflight a run request by resolving, optionally picking, and normalizing the execution plan.
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
app | string | Yes | Primary name query (aliases q, name) |
os | string | No | Target OS filter |
kind | string | No | App kind filter |
source | array | No | Source kind filter (repeatable). Empty means no filter |
arch | string | No | Target CPU architecture filter |
tags | array | No | Free-form selector tags (repeatable). Accepted and passed through to sources and recipe templates; they do not filter or rank candidates |
profile | string | No | Named profile for default preferences |
channel | string | No | Release channel hint |
version | string | No | Exact version or provider-defined version constraint |
variant | string | No | Provider-defined variant hint |
publisher | string | No | Publisher hint for curated registries |
repo | string | No | Repository hint such as owner/name |
release | string | No | Release hint such as a tag name |
asset | string | No | Desired asset name or pattern |
pick | string | No | Candidate selection mode (ask, first, index, id) |
pick_index | integer | No | Candidate index (required when pick=index) |
candidate_id | string | No | Specific candidate ID (required when pick=id) |
set_id | string | No | Bind pick to a specific candidate set |
terminal_id | integer | No | Terminal session ID. Default: 1 |
display | string | No | X11 DISPLAY number |
origin | string | No | Origin identifier for observability propagation |
format | string | No | Output format (json or html) |
dry_run | boolean | No | Force command-only output. hoody-run never executes |
print_curl | string | No | Generate curl command |
limit | integer | No | Max candidates. Default: 25 |
Response
Section titled “Response”curl -X POST "https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/preflight" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "app": "firefox", "os": "linux", "kind": "gui", "pick": "first" }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.run.test({ app: 'firefox', os: 'linux', kind: 'gui', pick: 'first' });{ "set_id": "a1b2c3d4e5f6", "selected": { "candidate_id": "nix-firefox-128", "title": "Firefox (nixpkgs)", "description": "Mozilla Firefox web browser via nixpkgs", "kind": "gui", "version": "128.0.3", "provider": "nix", "source_id": "nixpkgs", "score": 95, "run_plan": { "command": "nix run nixpkgs#firefox" }, "shell_command": "nix run nixpkgs#firefox" }, "shell_command": "nix run nixpkgs#firefox", "recommended_mode": "dry-run", "handoff": { "state": "preview", "terminal_id": 2, "display": ":2", "preview_display_url": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-display-2.node-example-1.containers.hoody.com/", "preview_terminal_url": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-terminal-2.node-example-1.containers.hoody.com/" }, "missing_requirements": [], "warnings": [], "effective_policy": { "require_verified": false, "require_integrity": false, "deny_providers": [], "deny_source_ids": [] }}{ "error": "invalid selector", "code": "INVALID_SELECTOR", "status": 400}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_APP | Missing app query | No app name was provided in the request | Set app to the desired program name |
INVALID_SELECTOR | Invalid selector parameter | One or more selector fields could not be parsed | Check enum values and numeric fields, then retry |
UNKNOWN_PROFILE | Unknown profile | The requested profile does not exist | Choose a profile returned by listProfiles or getConfig |
INVALID_PICK | Invalid pick request | The pick mode requirements were not satisfied or the selected candidate was not found | Search first, then supply a valid pick_index or candidate_id |
NO_CANDIDATES | No candidates | A pick was requested but the search found no candidate to select | Widen the selector (source, os, kind) or check source health |
{ "error": "candidate denied by policy", "code": "POLICY_DENIED", "status": 403}| Error Code | Title | Description | Resolution |
|---|---|---|---|
POLICY_DENIED | Candidate denied by policy | A pick from an older cached set resolved to a candidate that the current effective policy forbids | Pick a candidate allowed by the active profile, or search again under the current policy |
{ "error": "candidate set expired", "code": "SET_EXPIRED", "status": 409}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SET_EXPIRED | Candidate set expired | The set_id-bound candidate set has expired; pick=index can no longer be resolved race-safely | Re-run search to obtain a fresh set_id, then retry the pick |
{ "error": "source resolution failed", "code": "SOURCE_RESOLUTION_FAILED", "status": 502}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SOURCE_RESOLUTION_FAILED | Source resolution failed | Candidate resolution could not be completed because upstream source work failed | Retry or inspect provider/source health |
Batch execution
Section titled “Batch execution”Process multiple search or command-only run items in a single request. Each item produces its own success or error payload under its request_id.
POST /api/v1/run/batch
Section titled “POST /api/v1/run/batch”Execute a batch of search or run items. Each item’s mode selects between search (return the candidate list and set_id) and run (return the resolved shell command and handoff).
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
items | array | No | Batch items. Each item has request_id, mode, and selector |
Each items[] entry has the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
request_id | string | Yes | Caller-supplied identifier echoed back in the result |
mode | string | Yes | Batch item mode (search or run) |
selector | object | Yes | The Selector for this item (same shape as the JSON body of POST /api/v1/run/resolve) |
Response
Section titled “Response”curl -X POST "https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/batch" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "items": [ { "request_id": "search-firefox", "mode": "search", "selector": { "app": "firefox", "kind": "gui", "os": "linux" } }, { "request_id": "run-firefox", "mode": "run", "selector": { "app": "firefox", "kind": "gui", "os": "linux", "pick": "first" } } ] }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.run.resolveMany({ items: [ { request_id: 'search-firefox', mode: 'search', selector: { app: 'firefox', kind: 'gui', os: 'linux' } }, { request_id: 'run-firefox', mode: 'run', selector: { app: 'firefox', kind: 'gui', os: 'linux', pick: 'first' } } ]});{ "items": [ { "result": "search", "request_id": "search-firefox", "search": { "set_id": "a1b2c3d4e5f6", "candidates": [ { "candidate_id": "nix-firefox-128", "title": "Firefox (nixpkgs)", "description": "Mozilla Firefox web browser via nixpkgs", "kind": "gui", "version": "128.0.3", "provider": "nix", "source_id": "nixpkgs", "score": 95, "run_plan": { "command": "nix run nixpkgs#firefox" } } ] } }, { "result": "run", "request_id": "run-firefox", "run": { "status": "dry-run", "set_id": "a1b2c3d4e5f6", "shell_command": "nix run nixpkgs#firefox", "handoff": { "state": "preview", "terminal_id": 2, "display": ":2", "preview_display_url": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-display-2.node-example-1.containers.hoody.com/", "preview_terminal_url": "https://67e89abc123def456789abcd-890abcdef12345678901cdef-terminal-2.node-example-1.containers.hoody.com/" }, "warnings": [] } } ]}A failed item does not fail the batch: it carries an error object (error, code, status) in place of search or run, and the other items are still processed.
{ "error": "items must be an array", "code": "INVALID_BODY", "status": 400}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_JSON | Malformed JSON | The request body is not valid JSON | Send a JSON object with an items array |
INVALID_BODY | Invalid request body | The body is not an object, or items is not an array | Send a JSON object with an items array |
Configuration and health
Section titled “Configuration and health”Read the persisted runtime configuration and check service health.
GET /api/v1/run/config
Section titled “GET /api/v1/run/config”Return the full persisted runtime configuration including sources, profiles, and the currently selected profile.
This endpoint takes no parameters.
Response
Section titled “Response”curl "https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/config" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.run.config.get();{ "version": 3, "sources": [ { "source_id": "nixpkgs", "enabled": true, "priority": 100, "provider": "nix", "source_type": "nix-pkgs", "config": { "flake": "nixpkgs" } } ], "profiles": [ { "name": "default", "description": "Default profile (inherits global sources)", "defaults": { "os": "linux", "kind": "any", "pick": "first", "terminal_id": 1 }, "sources_mode": "inherit", "sources": [] } ], "selected_profile": "default", "recipes": [], "webhooks": []}GET /api/v1/run/health
Section titled “GET /api/v1/run/health”Return the standardized health response. Unauthenticated. Always returns HTTP 200 with application/json when the service is up.
This endpoint takes no parameters.
Response
Section titled “Response”curl "https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/health"{ "status": "ok", "service": "hoody-run", "built": "2026-01-15T12:00:00Z", "started": "2026-01-20T08:30:00Z", "memory": { "rss": 12582912, "heap": null }, "fds": 8, "pid": 42, "ip": "10.0.0.5"}