> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tryflowy.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Flowy is a node-based AI creative platform: you generate images, video, audio, 3D and vector on an infinite Canvas, refine on the Studio timeline, and export or publish from the same project.
> Prefer the Flowy MCP server (https://mcp.tryflowy.ai/mcp) or the REST API at https://apis.tryflowy.ai/v1 for programmatic work. Install with `flowy mcp install` from the @flowy/cli package.
> Credits are workspace-scoped. Generations reserve credits on start and only deduct on success, so failed runs refund automatically.

# Run a flow

> The end-to-end path: find a flow, send inputs, and poll for the result.

Running a flow is **asynchronous**: you start a run and get a `runId` back immediately, then poll until it completes. This guide walks through the whole thing.

All examples assume you've set `FLOWY_API` and `FLOWY_KEY` from [Authentication](/api/authentication).

<Note>
  This touches all three [scopes](/api/permissions): listing and fetching a flow needs `apps:read`, starting the run needs `runs:write`, and polling needs `runs:read`. A **Full**-access key has all three; a **Read-only** key can do every step except start the run.
</Note>

<Steps>
  <Step title="Find the flow you want to run">
    List the flows in your workspace and grab a flow's `id`.

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}}
      curl "$FLOWY_API/apps" -H "Authorization: Bearer $FLOWY_KEY"
      ```

      ```javascript Node.js theme={"theme":{"light":"github-light","dark":"vesper"}}
      const res = await fetch(`${BASE}/apps`, {
        headers: { Authorization: `Bearer ${KEY}` },
      });
      const { data: apps } = await res.json();
      ```

      ```python Python theme={"theme":{"light":"github-light","dark":"vesper"}}
      apps = requests.get(
          f"{BASE}/apps", headers={"Authorization": f"Bearer {KEY}"}
      ).json()["data"]
      ```
    </CodeGroup>

    ```json Response theme={"theme":{"light":"github-light","dark":"vesper"}}
    { "data": [
      { "id": "507f1f77bcf86cd799439011", "title": "Headshot generator",
        "published": true, "runCount": 42 }
    ] }
    ```
  </Step>

  <Step title="Look up the flow's inputs">
    Fetch the flow to see which inputs it expects. Each input's `name` (its kebab-cased title) is the `node_id` you'll send in the run request.

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}}
      curl "$FLOWY_API/apps/APP_ID" -H "Authorization: Bearer $FLOWY_KEY"
      ```

      ```javascript Node.js theme={"theme":{"light":"github-light","dark":"vesper"}}
      const res = await fetch(`${BASE}/apps/APP_ID`, {
        headers: { Authorization: `Bearer ${KEY}` },
      });
      const { data: app } = await res.json();
      ```

      ```python Python theme={"theme":{"light":"github-light","dark":"vesper"}}
      app = requests.get(
          f"{BASE}/apps/APP_ID", headers={"Authorization": f"Bearer {KEY}"}
      ).json()["data"]
      ```
    </CodeGroup>

    ```json Response theme={"theme":{"light":"github-light","dark":"vesper"}}
    { "data": {
      "id": "APP_ID", "title": "Headshot generator",
      "inputs": [
        { "nodeId": "node_1", "name": "selfie", "label": "Your photo", "kind": "image_url", "required": true },
        { "nodeId": "node_2", "name": "style", "label": "Style", "kind": "text", "required": false },
        { "nodeId": "node_3", "name": "references", "label": "Reference images", "kind": "image_url", "required": true,
          "arity": { "min": 1, "max": 4 } }
      ],
      "outputs": [ { "nodeId": "node_9", "name": "headshot", "label": "Headshot", "outputType": "image" } ],
      "params": [
        { "nodeId": "node_5", "name": "hero-video-resolution", "label": "Resolution", "group": "Hero video",
          "kind": "enum", "default": "720p",
          "enumValues": [
            { "value": "480p", "label": "480p" },
            { "value": "720p", "label": "720p" },
            { "value": "1080p", "label": "1080p" },
            { "value": "4k", "label": "4k" }
          ] }
      ]
    } }
    ```

    Use each input's **`name`** (here `selfie`, `style`, `references`) as the `node_id` in the run request below.

    An input carrying **`arity`** is a **variable input**: the run accepts between `arity.min` and `arity.max` values for that one key. Send them as an **array-valued** `asset_url` (or `prompt` for text). Values beyond `arity.max` are ignored.

    <Tip>
      **Advanced options.** `params` lists the generation settings the flow's author chose to expose (resolution, duration, voice, …), separate from `inputs`, and always optional. Each has a `default` (the node's published value at the time the flow was published) and, for `enum`/`number` kinds, the allowed `enumValues` / `min` / `max` / `step`; `group` names the node it belongs to. Send a param's **`name`** as a key in the run request's `params` object (next step). Omit any you don't want to override. The run uses its published default.
    </Tip>
  </Step>

  <Step title="Start the run">
    `POST` to the flow's `runs` endpoint with an `inputs` **array**, unchanged from before, plus an optional `params` **object** for anything from the previous step's `params` list. Each `inputs` entry's `node_id` is a content input's `name`; set its value with **`asset_url`** for media inputs (a URL) or **`prompt`** for text inputs (the text itself), an **array** for a variable input (`arity`). Each `params` entry is keyed by the param's **`name`**, with a value matching its `kind` (a string for `enum`/`text`, a number, or a boolean). You get a `runId` back right away.

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}}
      curl -X POST "$FLOWY_API/apps/APP_ID/runs" \
        -H "Authorization: Bearer $FLOWY_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "inputs": [
            { "node_id": "selfie", "asset_url": "https://example.com/me.jpg" },
            { "node_id": "style", "prompt": "studio lighting, neutral background" },
            { "node_id": "references",
              "asset_url": ["https://example.com/ref-1.jpg", "https://example.com/ref-2.jpg"] }
          ],
          "params": { "hero-video-resolution": "1080p" }
        }'
      ```

      ```javascript Node.js theme={"theme":{"light":"github-light","dark":"vesper"}}
      const res = await fetch(`${BASE}/apps/APP_ID/runs`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          inputs: [
            { node_id: "selfie", asset_url: "https://example.com/me.jpg" },
            { node_id: "style", prompt: "studio lighting, neutral background" },
            {
              node_id: "references",
              asset_url: ["https://example.com/ref-1.jpg", "https://example.com/ref-2.jpg"],
            },
          ],
          params: { "hero-video-resolution": "1080p" },
        }),
      });
      const { data } = await res.json();
      const runId = data.runId;
      ```

      ```python Python theme={"theme":{"light":"github-light","dark":"vesper"}}
      res = requests.post(
          f"{BASE}/apps/APP_ID/runs",
          headers={"Authorization": f"Bearer {KEY}"},
          json={
              "inputs": [
                  {"node_id": "selfie", "asset_url": "https://example.com/me.jpg"},
                  {"node_id": "style", "prompt": "studio lighting, neutral background"},
                  {"node_id": "references", "asset_url": [
                      "https://example.com/ref-1.jpg", "https://example.com/ref-2.jpg"]},
              ],
              "params": {"hero-video-resolution": "1080p"},
          },
      )
      run_id = res.json()["data"]["runId"]
      ```
    </CodeGroup>

    ```json Response theme={"theme":{"light":"github-light","dark":"vesper"}}
    { "data": { "runId": "64c3f2a1e8b9c0d1f2e3a4b5", "status": "queued" } }
    ```

    <Note>
      `inputs` stays lenient: an unrecognized `node_id` or a missing required content input never fails the request. Each is reported back as a `warnings` entry instead (`unknown_input` / `missing_input`), and the run still starts:

      ```json Response with warnings theme={"theme":{"light":"github-light","dark":"vesper"}}
      { "data": {
        "runId": "64c3f2a1e8b9c0d1f2e3a4b5", "status": "queued",
        "warnings": [
          { "code": "unknown_input", "field": "not_a_real_node",
            "message": "no input named \"not_a_real_node\", valid names: selfie, style, references" }
        ]
      } }
      ```

      `params` is the opposite: it's new, so it's strict. An unknown name or an out-of-range/wrong-type value is rejected with `400` before the run starts. See [Errors](/api/errors) for `unknown_param` and `invalid_param`.
    </Note>
  </Step>

  <Step title="Poll until it's done">
    Poll the run every couple of seconds until `status` is `completed` or `failed`. There's no separate `outputs` array. A run's result surface is `nodeResults`, its per-node record; the entries with `isOutput: true` are the ones the flow publishes (each carrying `name`, `kind`, and `url` or `text`). Output media URLs are signed and ready to download.

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"vesper"}}
      curl "$FLOWY_API/runs/RUN_ID" -H "Authorization: Bearer $FLOWY_KEY"
      ```

      ```javascript Node.js theme={"theme":{"light":"github-light","dark":"vesper"}}
      async function waitForRun(runId) {
        while (true) {
          const res = await fetch(`${BASE}/runs/${runId}`, {
            headers: { Authorization: `Bearer ${KEY}` },
          });
          const { data } = await res.json();
          if (data.status === "completed") {
            return data.nodeResults.filter((n) => n.isOutput);
          }
          if (data.status === "failed") throw new Error(data.error);
          await new Promise((r) => setTimeout(r, 2500));
        }
      }
      ```

      ```python Python theme={"theme":{"light":"github-light","dark":"vesper"}}
      import time

      def wait_for_run(run_id):
          while True:
              data = requests.get(
                  f"{BASE}/runs/{run_id}",
                  headers={"Authorization": f"Bearer {KEY}"},
              ).json()["data"]
              if data["status"] == "completed":
                  return [n for n in data["nodeResults"] if n.get("isOutput")]
              if data["status"] == "failed":
                  raise RuntimeError(data["error"])
              time.sleep(2.5)
      ```
    </CodeGroup>

    ```json Response theme={"theme":{"light":"github-light","dark":"vesper"}}
    { "data": {
      "runId": "RUN_ID", "appId": "APP_ID", "status": "completed",
      "nodeResults": [
        { "nodeId": "node_9", "name": "headshot", "kind": "image",
          "url": "https://cdn.tryflowy.ai/runs/headshot.png", "isOutput": true, "status": "completed" }
      ]
    } }
    ```

    A node absent from `nodeResults` was never executed (downstream of a failure, or a pass-through constant). A failed run's non-output entries, `status: "failed"` with an `error`, are there for debugging, not part of the published result.
  </Step>
</Steps>

<Tip>
  Output URLs are signed and expire. Download or copy the asset to your own storage soon after the run completes rather than storing the URL long-term.
</Tip>
