---
title: Trigger Recipes from an Agent (HTTP)
---

# Trigger Recipes from an Agent (HTTP)

## What it is

Omniscio exposes two HTTP endpoints on its localhost CLI Control server (`127.0.0.1:19519`) that let an AI agent — Claude Code, the in-app chat panel, or any tool holding the Omniscio bearer token — fire a multi-step recipe workflow without a human-approval click in the inbox. The user controls which recipes are agent-fireable through a per-recipe **Agent triggering** card in the Recipe Editor: open a recipe, scroll to the "Agent triggering" section, flip the toggle on, optionally set a cost cap in dollars, save. From that moment forward, any agent presenting the bearer token can run that recipe via `POST /recipes/run`.

This is intentionally different from the rest of CLI Control's mutation endpoints (cron jobs, automation rules, settings patches, session lifecycle), which queue a row in the inbox and refuse to fire until the user approves each instance. Recipes use **per-recipe pre-approval** instead — the user grants permission once when they flag the recipe, and agents act inside those permissions until the user toggles it off.

The two endpoints are:

- **`POST /recipes/run`** — fire a flagged recipe. Returns a `runId` and the `omniscio://orchestration/run/<runId>` deep-link the user can open to watch progress. A 30-second cooldown per `(recipeId, projectId)` tuple stops accidental retry-loop double-fires; pass a stable `Idempotency-Key` header to make a retry safe beyond that window (returns the original run, no second paid spawn).
- **`GET /recipes/triggerable`** — list the recipes the user has flagged, with parameter schemas, scope, home project id, and cost cap. Read-only and counts toward zero rate-limit budget.

## Where to find it

### How to use it

#### 1. Discover what's available

```bash
TOKEN=$(<~/.amc/cli-token)
curl -s -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:19519/recipes/triggerable
```

Response:

```json
{
  "ok": true,
  "recipes": [
    {
      "id": "8f3c9c2e-...",
      "name": "Audit Pipeline (5-prompt test)",
      "description": "Run a fixed audit pipeline against a project.",
      "scope": "project",
      "homeProjectId": "proj_abc123",
      "parameters": [
        {
          "name": "audit_prompts",
          "label": "Audit prompts (newline-separated)",
          "type": "text",
          "required": true,
          "default": "..."
        }
      ],
      "costCapUsd": 5
    }
  ]
}
```

If the array is empty, no recipes are flagged. Tell the user to open Omniscio → Recipes → pick a recipe → "Agent triggering" card → enable.

#### 2. Fire a recipe

```bash
curl -s -X POST http://127.0.0.1:19519/recipes/run \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "recipeName": "Audit Pipeline (5-prompt test)",
    "parameters": { "audit_prompts": "step1\nstep2\nstep3" }
  }'
```

Response (HTTP 200):

```json
{
  "ok": true,
  "data": {
    "runId": "run_xyz789",
    "dashboardUrl": "omniscio://orchestration/run/run_xyz789"
  }
}
```

Body fields:

- **At least one of `recipeId` or `recipeName`** is required. Prefer `recipeId` (canonical, survives renames). `recipeName` matches case- and whitespace-insensitively across global and every project's `.claude/recipes/` directory.
- **`projectName`** (optional) — overrides the recipe's `homeProjectId`. Use it when the user says "run X on project Y". Resolution is case-insensitive on the project name. If the recipe has no `homeProjectId` and the body omits `projectName`, the call returns 400.
- **`parameters`** (optional object) — keys must match the recipe's declared parameter `name`s. Required parameters without a `default` MUST be present. Extra keys are stripped silently.
- **`clientRequestId`** (optional) — an idempotency key. Send the SAME value (as the `X-Client-Request-Id` header or this body field; the header wins if both are present) when you retry the same logical trigger, and Omniscio returns the **original** run instead of spawning a second paid run. Unlike the 30s cooldown, this is safe across the full retry horizon (a 10-minute window) — use it whenever a network layer or load balancer might re-send a timed-out `POST`. Without it, retries are guarded only by the best-effort cooldown below.

#### 3. Poll status (optional)

The trigger is fire-and-forget — the HTTP call returns as soon as the engine accepts the dispatch. To follow the run, open the `dashboardUrl` (an `omniscio://` deep link Omniscio handles natively) or hit `GET /status` for a coarse summary.

### Pre-approval — how the user opts in

Recipes default to **agent-fireable = OFF**. The toggle lives on the Recipe Editor's **Agent triggering** card (it sits below the parameters card and above the supervisors section). Flipping it on writes `agentTriggerable: { enabled: true, costCapUsd?: <number> }` into the recipe's JSON and saves it. The flag is per-recipe — turning it on for one recipe does not auto-flip others.

The cost cap field is optional. If the recipe already declares a `limits.costCapUsd`, that wins. If it doesn't, the agent-trigger cap is copied into `limits.costCapUsd` for this run only — every agent-triggered run inherits the cap, the engine's existing cost-recovery logic kills the run if the cap is hit. Setting `agentTriggerable.costCapUsd` to `0` means "fire freely without a per-run cap"; setting it to a positive number means "stop the run at this dollar amount".

The toggle stays put until the user flips it off. There is no quota on triggers per recipe (yet) — the only throttle is the 30-second per-tuple cooldown.

**The agent-trigger flag is orthogonal to the authoring approval gate.** A recipe saved via the CLI authoring path (external AI → `POST /recipe/save`) lands as `approvalStatus: 'pending'` and the agent-trigger endpoint still refuses to dispatch it — even with `agentTriggerable.enabled: true` — until the user approves it in the Omniscio inbox. Two separate decisions: (1) "is this recipe authorized to exist?" (the authoring approval gate, blocking until inbox approval) and (2) "is this recipe authorized to be agent-fired without a per-run inbox click?" (the `agentTriggerable` toggle). Both must pass for `POST /recipes/run` to succeed. See [recipes-cli-authoring.md](recipes-cli-authoring.md) for the authoring gate.

## How it behaves

### Error codes

| Status | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                             | Recover                                                                                                                                                                                                                                                                                  |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | Two cases. (a) Validation error — body missing both `recipeId` and `recipeName`, missing required parameter, or `projectName` could not be resolved. (b) `error: "Recipe is pending approval..."` or `"Recipe was rejected..."` — the matched recipe has `approvalStatus: 'pending'` or `'rejected'` and is awaiting authoring approval in the Omniscio inbox. The `agentTriggerable.enabled: true` flag does NOT bypass this gate. | (a) Read the `error` field, fix the body, retry. (b) Tell the user to approve the recipe in the Omniscio inbox (or revise + re-approve a rejected recipe). Do NOT auto-retry — authoring approval is a human-in-the-loop step. See [recipes-cli-authoring.md](recipes-cli-authoring.md). |
| 401    | `error: "unauthorized"` — Bearer token missing or invalid. The dispatcher authenticates every non-GET method before the handler runs, so this is identical to every other Omniscio CLI mutation's auth-failure response. As of L05-F10 the whole CLI surface is uniform — a missing/invalid token is always `401`, never `403`.                                                                                                     | Tell the user to regenerate the token in Omniscio Settings → CLI Control. The auto-delivered file at `~/.amc/cli-token` is the source of truth.                                                                                                                                          |
| 403    | `error: "Recipe '...' is not authorized for agent triggering"` — the matched recipe does NOT have `agentTriggerable.enabled: true`. Authenticated-but-denied: the token is valid; the recipe just isn't flagged. (A **missing or invalid** token returns **401 `unauthorized`** at the dispatcher instead — see the 401 row above.)                                                                                                 | Tell the user "That recipe isn't flagged for agent triggering — open the Recipe Editor → Agent triggering card → enable, then retry." Do NOT retry without the user flipping it.                                                                                                         |
| 404    | No recipe matched the given `recipeId` or `recipeName` (searched global and every project's `.claude/recipes/` directory).                                                                                                                                                                                                                                                                                                          | Re-list with `GET /recipes/triggerable` — the recipe may have been deleted, renamed, or the flag turned off.                                                                                                                                                                             |
| 409    | The orchestration engine is already running another top-level recipe — engine enforces a singleton lock so it will not run two recipes in parallel.                                                                                                                                                                                                                                                                                 | Tell the user "Another recipe is currently running — wait for it to finish, then retry." Don't auto-retry on a fixed schedule; the wait is unbounded.                                                                                                                                    |
| 429    | Either the shared 10-mutations/minute limit is hit, OR the per-`(recipeId, projectId)` cooldown is active. Cooldown response includes `retryAfterSeconds`.                                                                                                                                                                                                                                                                          | If `retryAfterSeconds` is present, wait that long and retry. Otherwise wait 60 seconds for the rate-limit bucket to refill. Do NOT bypass the cooldown by varying request shape — it is keyed on the canonical tuple, server-side.                                                       |
| 500    | Engine spawn failed (process error, file-store error, etc.).                                                                                                                                                                                                                                                                                                                                                                        | Surface the `error` field to the user and tell them to check Omniscio's logs (Settings → Debug Log Viewer).                                                                                                                                                                              |

### Cooldown rules

- Keyed on `(recipeId, projectId)`. Same recipe on a different project is a different tuple and is allowed.
- Window: 30 seconds. There is no flag to change this.
- Triggered on every successful dispatch. Engine-busy 409s do NOT consume the cooldown — the user can immediately retry once the busy run finishes.
- In-memory only. An Omniscio restart resets every cooldown.

The cooldown exists to absorb agent retry loops. If a Claude Code session sees a transient failure and retries the same `POST /recipes/run` immediately, the second call is rejected with 429 + `retryAfterSeconds: <0..30>` so it can back off cleanly. Without the cooldown, a misbehaving agent could fire dozens of expensive runs in seconds.

The cooldown is a best-effort timestamp window, not true idempotency — a retry that arrives **more than 30 seconds** later (a slow client, or a load balancer re-sending a timed-out `POST`) passes the expired cooldown and would double-fire. To make such a retry safe, send a stable **`Idempotency-Key`** (`X-Client-Request-Id`) header: a repeat with the same key returns the original run for up to 10 minutes instead of spawning a second paid run. See the `clientRequestId` body field above.

### Cost cap

If the recipe carries `agentTriggerable.costCapUsd > 0` AND no `limits.costCapUsd`, the agent-trigger cap is copied into `limits.costCapUsd` for the run before dispatch. If the recipe already has a `limits.costCapUsd`, that wins (lowering at the recipe level is intentional). The engine's existing cost-cap recovery logic kills any step whose cumulative spend exceeds the cap and surfaces a "cost cap hit" notification.

This makes the agent-trigger flag a budget surface: the user can flag a recipe as agent-fireable AND cap each run at $5 in the same UI card, without authoring a separate `limits` block.

## For agents

### How it works

The HTTP route in [src/main/services/cli/cli-server.ts](/src/main/services/cli/cli-server.ts) is a thin wrapper over the IPC handler at [src/main/ipc/recipe-handlers.ts](/src/main/ipc/recipe-handlers.ts) (`handleRecipeTriggerViaCli`). Both share the same validation order, the same in-memory cooldown `Map<string, number>` keyed on `<recipeId>:<projectId>`, and the same dispatch into the recipe orchestration engine. Error mapping happens in the HTTP layer so callers always get matching status codes regardless of whether they came in over IPC or HTTP.

Recipe lookup mirrors `GET /recipes/triggerable` — it calls `listAllRecipeConfigs(projectPaths)` against the global directory plus every registered project's `.claude/recipes/` directory and dedupes by id (global wins on collision). This is critical for project-scoped recipes: a recipe authored in `<projectPath>/.claude/recipes/` is reachable for triggering as long as the project is registered in Omniscio. (Earlier builds searched only the global directory and broke project-scoped triggering — fixed in commit `af794f9`.)

Every successful trigger logs a `recipe.agent_triggered` feature event with metadata `{ recipeId, recipeName, source, projectId }`, where `source` is either `'cli'` (HTTP route) or `'ipc'` (in-app chat panel). The same event surface is used by the analytics dashboard.

The flag itself lives in the recipe JSON under `agentTriggerable: { enabled, costCapUsd? }`. The Recipe Editor's "Agent triggering" card binds to that key — see [src/renderer/src/features/recipes/RecipeEditor.tsx](/src/renderer/src/features/recipes/RecipeEditor.tsx) (search for "Agent triggering"). Flipping the toggle persists through the standard recipe save flow with no separate IPC channel needed.

## Related

- [use-recipes.md](use-recipes.md) — building, running, and scheduling recipes (the human-driven flow)
- [recipes-cli-authoring.md](recipes-cli-authoring.md) — authoring a recipe by asking an external AI (lands as `pending` until inbox approval)
- [cli-control.md](cli-control.md) — full CLI Control Server endpoint reference
- [omniscio-control skill](/.claude/skills/omniscio-control/SKILL.md) — the same endpoints documented for in-conversation external-AI use
