# Minion - documentation for language models Minion is an AI agent workspace: it manages agents ("minions") that run on your own hosts, and it carries the workspace tools those agents and their humans share - notes, forms, calendar, drive, accounting, assessments, and a headless CMS. This file contains only the pages needed to integrate a webhook workflow trigger. Each page begins with its canonical URL. Full documentation: https://docs.minionworkspace.com/llms-full.txt Japanese: https://docs.minionworkspace.com/ja/llms-dag-webhook.txt ## Rules that are easy to get wrong Read these before writing code. They are the points where a reasonable-looking implementation is the wrong one. 1. **Call the webhook from a server, a CLI or an extension's background worker — never from a web page.** It returns no CORS headers, and the whole URL (`https:///api/dag/webhooks/`) is the credential: there is no auth header and no signature. Keep it in a server-side variable, never a `NEXT_PUBLIC_` / `PUBLIC_` one. 2. **The body is one JSON object with no fixed fields, and it is not wrapped.** It becomes the start node's `output_data` verbatim, so there is no `title` / `body` / `payload` envelope to fill in. Arrays, scalars and an empty body are rejected with `400` — send `{}` when there is nothing to pass. `Content-Type: application/json` is required and the cap is 1MB. 3. **Nothing here is idempotent.** `Idempotency-Key` is not read. Retrying after a timeout can produce a second run, because the first may exist even though no `201` came back. Put your own unique id in the body and dedupe inside the workflow, or do not retry automatically. 4. **`201` means accepted, not finished, and no API returns the result.** Reading a run's state needs a logged-in session, so the token cannot poll it. If the caller needs the outcome, end the workflow with a node that POSTs back to you. 5. **The workflow may have fixed the payload's shape.** If the start node declares an `input_contract`, or start's outgoing edges carry contracts, a mismatched body is rejected with `400` and a `violations` array before any node runs. A caller cannot detect this in advance — ask whoever built the workflow. 6. **Retrying a `4xx` changes nothing, and `404` is deliberately ambiguous**: wrong token, disabled webhook and inactive placement all answer the same way. Note also that disabling a webhook discards its token, so re-enabling issues a different URL. 7. **Send images and files as publicly fetchable URLs, not base64.** Only JSON is accepted, base64 counts against the 1MB cap (roughly 700KB of image) and is stored with the run. A URL behind a login is no use either — the minion cannot fetch that one. --- # Triggering a workflow by webhook Source: https://docs.minionworkspace.com/guides/dag-webhook/ Summary: Everything needed to start a DAG workflow from an outside service, script or browser extension, on one page. A DAG workflow can hand you a **URL that starts it with a single POST**. A script on your machine, an internal service, or the "send" button in a browser extension can all run the workflow through it. This page is deliberately self-contained: hand it over and the integration can be written from it alone (whether the reader is a person or a coding assistant). A plain-text version for language models lives at [`/llms-dag-webhook.txt`](https://docs.minionworkspace.com/llms-dag-webhook.txt). ## The whole thing in three lines Place a workflow in a project and that placement can issue a URL containing a secret token. POST a JSON object to it and **the body becomes the start node's output verbatim**, reaching the downstream nodes in exactly that shape. The token in the URL is the only credential — no headers, no signature. ## Values you need before you start | Value | Shape | Where from | |---|---|---| | Webhook URL | `https://minionworkspace.com/api/dag/webhooks/` | Open the workflow in its project, then **Webhook Trigger** → **Enable** → **Copy** | - **The host is the same origin you open Minion on.** Neither the workspace id nor the project id appears in the path. The path is only `/api/dag/webhooks/`, and **the token decides where the request lands**. - **There is one URL per workflow × project.** If the same workflow is placed in two projects, the URLs differ, and the URL decides which project's members, roles and context the run uses. The caller cannot choose the project. - The token is 32 random bytes (base64url, 43 characters). **The whole URL is the credential**, so keep it in an environment variable (for example `MINION_WEBHOOK_URL`). Never prefix it with `NEXT_PUBLIC_`, `PUBLIC_` or `VITE_` — that embeds it in the JavaScript you ship to browsers. - The screen masks the token portion, but **Copy always copies the full URL**. It is not a show-once secret: if you lose it, take it from the screen again. ### Enabling, stopping, reissuing | Action | What happens | |---|---| | **Enable** | Issues a token and the URL starts working | | **Rotate token** | Issues a new token. **The old URL 404s immediately.** Use this if it leaks | | **Disable** | The URL stops working, and **the token is discarded** | **Do not use Disable as a pause.** Enabling again issues a **different URL**, so every caller has to be reconfigured. ## The request ```bash curl -i -X POST "$MINION_WEBHOOK_URL" \ -H 'content-type: application/json' \ -d '{"source": "chrome-extension", "url": "https://example.com/x/status/123"}' ``` ### Authentication **There is no auth header.** No `Authorization: Bearer ...`, no `X-...-KEY`, no signature. The token in the URL is the only credential. Extra headers are neither read nor rejected. Because the URL itself is the key, **keep it out of logs, error reports, `Referer` and screenshots**. ### Headers | Header | Required | Notes | |---|---|---| | `Content-Type: application/json` | **Yes** | Missing it gives `415`. `application/json; charset=utf-8` is fine | | Anything else | — | Not read | `POST` is the only method; others return `405`. ### The body **There are no fixed fields.** No `title` / `body` / `url` envelope is defined. Use **whatever keys and types you like** — strings, numbers, booleans, arrays, nested objects — and do not wrap them (`{"payload": {...}}` is not a thing here). The object you send **becomes the start node's `output_data` verbatim**, and downstream nodes read it in that shape. | Constraint | Detail | |---|---| | Top-level type | **One JSON object.** Arrays, `null`, numbers and strings give `400` | | Empty body | `400`. Send `{}` when you have nothing to pass | | Size | **1MB** (checked against `Content-Length`; over it gives `413`) | #### The receiving side may have fixed the shape If the workflow's [start node](https://docs.minionworkspace.com/skills-workflows/dag-nodes/start/) declares an `input_contract`, or the edges leaving start carry contracts, **the body must satisfy that type** — otherwise the run is rejected with `400` before a single node executes. The response's `violations` names each field and how it differs. **A caller cannot tell in advance whether a contract exists.** Ask whoever built the workflow whether start has one and, if so, for the field names and types. With no contract, any object passes. ## The response ### Success ``` HTTP/1.1 201 Created { "execution_id": "3f0c…", "root_nodes": 1, "total_nodes": 7 } ``` **`201` means accepted and started, not finished.** The workflow itself runs asynchronously on the minions afterwards. **There is no API for reading progress or results from outside.** `execution_id` identifies the run in the HQ screens, and the endpoint that returns a run's state requires a logged-in session, so the webhook token cannot read it. If the caller needs the outcome, have **the workflow** call your service (a skill or script node at the end that POSTs to you). ### Failure The body is always `{"error": ""}`, plus a `violations` array for contract violations. `error` is short enough to drop straight into a button `title` or a toast. | Status | Example `error` | Meaning / what to do | |---|---|---| | `400` | `Request body must be a JSON object` | An array, a scalar or an empty body was sent | | `400` | `Invalid JSON body` | Malformed JSON | | `400` | `Payload violates start input contract "X"` | `violations[]` has the specifics; fix the shape | | `400` | `Payload violates start outgoing edge contracts` | Same, for the contracts on start's outgoing edges | | `400` | `DAG workflow has no version` / `DAG workflow has empty graph` | The workflow is unsaved or empty; tell its author | | `400` | `No PM minion assigned to project` | The project has no minion with the PM role | | `404` | `Not found` | Wrong token / webhook disabled / placement inactive. **The three are indistinguishable** (so tokens cannot be probed) | | `409` | `Project is archived` | The project is frozen; only a person can unfreeze it in HQ | | `413` | `Payload exceeds 1000000 bytes` | Over 1MB | | `415` | `Content-Type must be application/json` | Missing header | | `500` | `Internal server error` | Transient failure | **Retrying a `4xx` gives the same answer**, so fix it first. Only `5xx` and timeouts are worth retrying — read the next section before you do. ## Deduplication **There is none.** The `Idempotency-Key` header is not read, and no body field serves that purpose. **POST the same body twice and two runs happen.** Retrying after a timeout is risky because **the run may already exist even though no `201` came back**. If you need protection, pick one: - Put a unique value in the body (`"source_id": "..."`) and let **the workflow** drop what it has already handled - Do not retry automatically — surface the failure and let the user press the button again (usually enough for an extension button) ## CORS **No CORS headers are returned.** A POST with `Content-Type: application/json` always triggers a preflight, so **calling it directly from a web page's JavaScript is blocked by the browser**. That is intended: the whole URL is a credential and does not belong in code you ship to browsers. | Caller | Works | |---|---| | A browser extension's background / service worker (with the HQ origin in `host_permissions`) | **Yes** | | A server, CLI, CI job or minion | **Yes** | | A web page's frontend, directly | **No** — go through your own server | ## Rate limits **The application applies none.** But each POST really does start a workflow and consume (billable) minion compute time, so a double-clicked button becomes a pile of runs. **Stop double submits on your side** — disable the button while a request is in flight. ## Images and other files - **This endpoint takes JSON only.** No multipart, no binary. - **Passing a URL is the straightforward option.** It keeps the request small, and the minion running the workflow can fetch it — provided **the URL needs no authentication**. An image that requires a login is out of reach for the minion too. - Base64 works but **counts against the 1MB cap**. Base64 inflates by roughly 1.37×, so the practical ceiling is an image of about 700KB. Anything larger should be a URL, or go through Drive first. - The body is stored as the run's record, so base64 inflates the execution history by the same amount. ## When it does not work - **Suspect `404` first.** Did you enable it, is this the URL from after the last rotate, and is the workflow's placement active? All three collapse into the same `404`. - **`201` but apparently nothing happens** — check the executions list in HQ. The run exists and its downstream nodes are either waiting to be claimed by a minion or have failed. - **`400` with `violations`** is a shape problem: the field names, types and missing entries are spelled out for you. ## The other ways to start a workflow | Way | Payload | Notes | |---|---|---| | Manual | Entered on screen | The run button in HQ | | Schedule (cron) | Always `{}` | 5 minute minimum interval; fired by the project's PM minion | | **Webhook** | The JSON object you POST | This page | --- # Start Node Source: https://docs.minionworkspace.com/skills-workflows/dag-nodes/start/ Summary: Entry sentinel for a DAG workflow The **Start** node is the entry point of a workflow graph. It receives the trigger payload and hands it to the first downstream node through the normal cascade. Start is inserted automatically when you create a new workflow and does not appear in the node palette — you cannot drag one onto the canvas. ## When to use Every top-level graph needs exactly one Start node. You do not need to add one inside a fan-out template — templates use their In/Out markers instead, and Start/End nodes are rejected inside templates. ## Configuration | Field | Description | |-------|-------------| | `input_contract` (optional) | Name of a contract that incoming trigger payloads must satisfy. Unset means any payload is accepted; cron-triggered runs always pass `{}`. | The contract is selected from the contracts defined on the graph. Payloads that fail validation are rejected with HTTP 400 before the run even starts. Payloads arrive from a manual run, from a cron schedule (always `{}`), or from an outside caller over a [webhook URL](https://docs.minionworkspace.com/guides/dag-webhook/). ## Behavior 1. When the workflow is triggered, the Start node is inserted as `completed` with `output_data` set to the trigger payload. 2. It immediately fires cascade to its downstream successors, who read the payload as their input. Start never runs on a minion and has no billable compute time. ## Validation rules - Exactly one Start node per top-level graph. - Start must not have incoming edges. - Start must not appear inside a fan-out template.