Run: Recipes
Section titled “Run: Recipes”A recipe is a named, reusable selector template that you can invoke later with optional overrides. The endpoints on this page let you manage the recipe store and run saved recipes without re-sending the full selector. Use them to package a frequently used resolution pattern (for example, “default Firefox on Linux x86_64”) once and reuse it across searches, dry-runs, and curl generation.
All endpoints below run on the per-container run service.
List and retrieve recipes
Section titled “List and retrieve recipes”GET /api/v1/run/recipes
Section titled “GET /api/v1/run/recipes”List every saved recipe. Each entry is a run_RecipeConfig describing the recipe’s name, optional description, selector template, and the list of selector fields a caller is allowed to override at run time.
This endpoint takes no parameters.
curl -X GET 'https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/recipes' \ -H 'accept: application/json'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.recipes.list();[ { "name": "firefox-default", "description": "Default Firefox launcher for Linux x86_64", "selector_template": { "app": "firefox", "os": "linux", "kind": "gui", "arch": "amd64", "pick": "first" }, "allowed_overrides": ["version", "channel"] }, { "name": "node-lts", "description": "Latest LTS Node.js runtime", "selector_template": { "app": "node", "os": "any", "kind": "cli", "channel": "lts", "pick": "first" }, "allowed_overrides": ["version"] }]GET /api/v1/run/recipes/{name}
Section titled “GET /api/v1/run/recipes/{name}”Fetch a single saved recipe by name.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | Recipe name |
curl -X GET 'https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/recipes/firefox-default' \ -H 'accept: application/json'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.recipes.get('firefox-default');{ "name": "firefox-default", "description": "Default Firefox launcher for Linux x86_64", "selector_template": { "app": "firefox", "os": "linux", "kind": "gui", "arch": "amd64", "pick": "first" }, "allowed_overrides": ["version", "channel"]}{ "error": "recipe not found", "code": "RECIPE_NOT_FOUND", "status": 404}| Error Code | Title | Description | Resolution |
|---|---|---|---|
RECIPE_NOT_FOUND | Recipe not found | No recipe exists with the requested name | Call client.run.recipes.list() and choose a valid recipe name |
Create and update recipes
Section titled “Create and update recipes”POST /api/v1/run/recipes
Section titled “POST /api/v1/run/recipes”Create a new named recipe. The response is the full updated list of recipes.
This endpoint takes no parameters.
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Recipe name |
description | string | No | Human-readable description |
selector_template | object | No | Partial selector template (see run_SelectorTemplate) |
allowed_overrides | array | No | Selector fields a caller may override when running this recipe. Default: [] |
curl -X POST 'https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/recipes' \ -H 'content-type: application/json' \ -d '{ "name": "firefox-default", "description": "Default Firefox launcher for Linux x86_64", "selector_template": { "app": "firefox", "os": "linux", "kind": "gui", "arch": "amd64", "pick": "first" }, "allowed_overrides": ["version", "channel"] }'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.recipes.create({ name: 'firefox-default', description: 'Default Firefox launcher for Linux x86_64', selector_template: { app: 'firefox', os: 'linux', kind: 'gui', arch: 'amd64', pick: 'first', }, allowed_overrides: ['version', 'channel'],});[ { "name": "firefox-default", "description": "Default Firefox launcher for Linux x86_64", "selector_template": { "app": "firefox", "os": "linux", "kind": "gui", "arch": "amd64", "pick": "first" }, "allowed_overrides": ["version", "channel"] }]{ "error": "missing recipe name", "code": "MISSING_RECIPE_NAME", "status": 400}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_RECIPE_NAME | Missing recipe name | The recipe did not include a non-empty name | Set name before creating the recipe |
INVALID_BODY | Invalid request body | The body does not fit RecipeConfig (for example an unknown pick mode in selector_template) | Check the field named in the error |
{ "error": "recipe already exists", "code": "RECIPE_ALREADY_EXISTS", "status": 409}| Error Code | Title | Description | Resolution |
|---|---|---|---|
RECIPE_ALREADY_EXISTS | Recipe already exists | A recipe with the same name already exists | Choose a unique name or update the existing recipe instead |
{ "error": "configuration save failed", "code": "CONFIG_SAVE_FAILED", "status": 503}| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONFIG_SAVE_FAILED | Configuration save failed | The updated recipe could not be persisted | Check storage health and retry |
PATCH /api/v1/run/recipes/{name}
Section titled “PATCH /api/v1/run/recipes/{name}”Partially update an existing recipe. Only the fields present in the body are applied; everything else keeps its stored value. The recipe’s name is taken from the path and cannot be changed here.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | Recipe name |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
description | string | No | Human-readable recipe description. Send null to clear it |
selector_template | object | No | Partial selector template (see run_SelectorTemplate) |
allowed_overrides | array | No | Selector fields a caller may override. Replaces the stored list as a unit |
curl -X PATCH 'https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/recipes/firefox-default' \ -H 'content-type: application/json' \ -d '{ "description": "Default Firefox launcher for Linux x86_64 (updated)", "allowed_overrides": ["version", "channel", "variant"] }'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.recipes.update('firefox-default', { description: 'Default Firefox launcher for Linux x86_64 (updated)', allowed_overrides: ['version', 'channel', 'variant'],});{ "name": "firefox-default", "description": "Default Firefox launcher for Linux x86_64 (updated)", "selector_template": { "app": "firefox", "os": "linux", "kind": "gui", "arch": "amd64", "pick": "first" }, "allowed_overrides": ["version", "channel", "variant"]}{ "error": "invalid recipe update", "code": "INVALID_RECIPE", "status": 400}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_RECIPE | Invalid recipe update | A field has a value its type refuses (for example an unknown pick mode in selector_template), the body is not an object, it names a field that cannot be patched, or it tries to change the name | Fix the field named in the error; nothing was stored |
INVALID_JSON | Malformed JSON | The request body is not valid JSON | Send a JSON object |
{ "error": "recipe not found", "code": "RECIPE_NOT_FOUND", "status": 404}| Error Code | Title | Description | Resolution |
|---|---|---|---|
RECIPE_NOT_FOUND | Recipe not found | No recipe exists with the requested name | Call client.run.recipes.list() and choose a valid recipe name |
{ "error": "configuration save failed", "code": "CONFIG_SAVE_FAILED", "status": 503}| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONFIG_SAVE_FAILED | Configuration save failed | The updated recipe could not be persisted | Check storage health and retry |
DELETE /api/v1/run/recipes/{name}
Section titled “DELETE /api/v1/run/recipes/{name}”Remove a saved recipe by name.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | Recipe name |
curl -X DELETE 'https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/recipes/firefox-default'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.recipes.delete('firefox-default');The response has no body.
{ "error": "recipe not found", "code": "RECIPE_NOT_FOUND", "status": 404}| Error Code | Title | Description | Resolution |
|---|---|---|---|
RECIPE_NOT_FOUND | Recipe not found | No recipe exists with the requested name | Call client.run.recipes.list() and choose a valid recipe name |
{ "error": "configuration save failed", "code": "CONFIG_SAVE_FAILED", "status": 503}| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONFIG_SAVE_FAILED | Configuration save failed | The updated recipe could not be persisted | Check storage health and retry |
Search and run with a recipe
Section titled “Search and run with a recipe”These two endpoints apply allowed overrides to a saved recipe and resolve it. Search returns the ranked candidate set so you can pick later; run resolves and selects a single candidate, returning either the exact shell command or a generated curl command.
POST /api/v1/run/recipes/{name}/search
Section titled “POST /api/v1/run/recipes/{name}/search”Resolve a recipe to a ranked candidate set after applying allowed overrides. The returned set_id can later be combined with pick=id on a run request for race-free selection.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | Recipe name |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
overrides | object | No | Partial selector template. Only fields listed in the recipe’s allowed_overrides are applied |
curl -X POST 'https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/recipes/firefox-default/search' \ -H 'content-type: application/json' \ -d '{ "overrides": { "version": "128.0.3" } }'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.recipes.search('firefox-default', { overrides: { version: '128.0.3', },});{ "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", "homepage": "https://www.mozilla.org/firefox/", "tags": ["browser", "web"], "provider": "nix", "source_id": "nixpkgs", "score": 95, "reasons": ["exact name match"], "run_plan": { "command": "nix run nixpkgs#firefox", "env": {}, "cwd": "/home/user" }, "verified": false } ]}{ "error": "override not allowed: version", "code": "OVERRIDE_NOT_ALLOWED", "status": 400}| Error Code | Title | Description | Resolution |
|---|---|---|---|
OVERRIDE_NOT_ALLOWED | Override not allowed | The request overrides a selector field that the recipe does not list in allowed_overrides | Remove the override named in the error, or add the field to the recipe’s allowed_overrides with client.run.recipes.update() |
UNKNOWN_PROFILE | Unknown profile | The requested profile does not exist | Choose a profile returned by client.run.profiles.list() or client.run.config.get() |
{ "error": "recipe not found", "code": "RECIPE_NOT_FOUND", "status": 404}| Error Code | Title | Description | Resolution |
|---|---|---|---|
RECIPE_NOT_FOUND | Recipe not found | No recipe exists with the requested name | Call client.run.recipes.list() and choose a valid recipe name |
{ "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/recipes/{name}/run
Section titled “POST /api/v1/run/recipes/{name}/run”Resolve and select a candidate from a saved recipe after applying allowed overrides. The status field of the response describes the outcome: resolved (candidates returned without a single selection), dry-run (one candidate selected and its shell command returned; hoody-run never executes), printed-curl (an equivalent curl command is returned instead), or error (resolution failed and a message is included).
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
name | path | string | Yes | Recipe name |
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
overrides | object | No | Partial selector template. Only fields listed in the recipe’s allowed_overrides are applied |
curl -X POST 'https://{projectId}-{containerId}-run-1.{server}.containers.hoody.com/api/v1/run/recipes/firefox-default/run' \ -H 'content-type: application/json' \ -d '{ "overrides": { "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.recipes.resolve('firefox-default', { overrides: { 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", "homepage": "https://www.mozilla.org/firefox/", "tags": ["browser", "web"], "provider": "nix", "source_id": "nixpkgs", "score": 95, "reasons": ["exact name match"], "run_plan": { "command": "nix run nixpkgs#firefox", "env": {}, "cwd": "/home/user" }, "verified": false }, "shell_command": "nix run nixpkgs#firefox", "handoff": { "state": "preview", "terminal_id": 1, "display": "Firefox (nixpkgs)" }, "warnings": []}{ "error": "override not allowed: version", "code": "OVERRIDE_NOT_ALLOWED", "status": 400}| Error Code | Title | Description | Resolution |
|---|---|---|---|
OVERRIDE_NOT_ALLOWED | Override not allowed | The request overrides a selector field that the recipe does not list in allowed_overrides | Remove the override named in the error, or add the field to the recipe’s allowed_overrides with client.run.recipes.update() |
UNKNOWN_PROFILE | Unknown profile | The requested profile does not exist | Choose a profile returned by client.run.profiles.list() or client.run.config.get() |
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": "recipe not found", "code": "RECIPE_NOT_FOUND", "status": 404}| Error Code | Title | Description | Resolution |
|---|---|---|---|
RECIPE_NOT_FOUND | Recipe not found | No recipe exists with the requested name | Call client.run.recipes.list() and choose a valid recipe name |
{ "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 |