# Flows

`GET | POST | PATCH | DELETE /v1/flows` — Chain renders, Clap's writing, posts and deliveries — start them by hand, by API, after a render, on a schedule or from a webhook (Zapier, Make, n8n).

- Base URL: https://eroq.ai/v1
- Auth: `Authorization: Bearer eroq_sk_…` (create keys at https://eroq.ai/dashboard/keys)
- Credits: Studio plan or higher — each render a run makes is charged by its own endpoint

A flow is a graph of steps — renders, Clap's words, a post, a webhook, a folder — drawn in the studio's Flows editor or sent here as JSON. Every render a run makes goes through its own endpoint (images, videos, speech): same prices, same plan gates, same refunds; Clap's writing is included. Flows belong to the workspace: teammates share them.

**Several ways to start one** — a flow carries up to one trigger of each kind, each with its own line into the steps: `trigger.manual` (« When I run it »: the studio's Run, or `POST /v1/flows/{id}/runs`; it can ask for a prompt and a picture at every run), `trigger.render` (« After a render »: a render that names the flow with `flow_id` on POST /v1/images/generations, /v1/videos/generations, /v1/videos/edits or /v1/audio/speech, a run with `inputs.creation_id`, « Run a flow » on a creation — and, when its `folderId` is set, everything saved or moved into that folder), `trigger.schedule` (every `day` or `week` at `at`, read in the `tz` time zone), `trigger.webhook` (« When a webhook is called »: a secret URL any app can POST to, Zapier, Make, n8n, a form, a shop; see below). A run starts from ONE trigger and runs what its lines reach.

**Runs happen on the server.** `POST /v1/flows/{id}/runs` answers 202 with the run; poll `GET /v1/flows/{id}/runs/{run_id}` — every look moves the run on, and runs nobody watches are moved on every minute — or pass `callback_url` and the finished run is POSTed there once, in the `flow.run.*` event shape (see Webhooks; `flow.run.stopped` when someone stopped it). Each step reports its `status`, what it made (`creationId` and a fresh signed `url`), its `text`, its `credits` and, when it failed, `error`. A **post is never sent by the API**: the « Post to socials » step makes the run wait (`status: "waiting"`, `waiting` names the clip, caption and platforms) until someone posts it — or lets it go — from the studio (TikTok and YouTube want a person's click per post); a post left waiting for 7 days is let go and the run carries on. `POST …/runs/{run_id}/stop` stops a run: steps not started are skipped, a render already sent still finishes and lands in the library.

The graph: `nodes` — `{ id, n, type, x, y, config }`, where `n` is the step's number (`{{#n}}` inserts what step n wrote, or the link of what it rendered, into any text) — and `edges` — `{ from, to }`, the ORDER (a step runs once every step leading to it is done). `{{#n.field}}` reads one field of a webhook's body (`{{#1.customer.name}}`). Types and their `config`: `trigger.manual` (`askPrompt`, `askImage`), `trigger.render` (`kind`: video · image · audio, `folderId`), `trigger.schedule` (`every`, `at` "HH:MM", `day` 0–6, `tz`), `trigger.webhook` (`promptField`: the field read as `{{#1}}`, empty = the whole body; `imageField`: a field holding an https picture; `salt`: a new value is a new URL), `generate.image` (`prompt`, `model`, `aspect`), `generate.video` (`prompt`, `model`, `seconds`, `aspect`, `startFrom` = an earlier image step), `generate.voice` (`prompt` = the words, `model`, `voice`), `clap.write` (`instruction`, `about` = an earlier step), `publish.social` (`platforms`, `clip` = an earlier clip step, `caption`), `send.webhook` (`url`, `message` — Discord and Slack show the message, other endpoints get the run as JSON), `library.folder` (`folderId`). The server re-sanitizes every graph it stores; send the one GET returns to keep what you do not change.

**Plan, draft or published.** Every plan builds flows. Running one, the studio's Run once included, and publishing it (`published: true`) come with the Studio plan or higher (403 `plan_required` below it). A draft runs only from the studio's editor; a published flow also starts by its schedule, its watched folder, `flow_id`, its webhook URL and `POST /v1/flows/{id}/runs` (a draft answers 409 `flow_not_published` there). Limits: 24 steps and 100 flows per workspace, 6 runs a minute per person, 50 automatic runs a day per flow (watched folder + schedule), 30 a minute and 500 a day from its webhook, the last 30 runs kept. A scheduled or webhook run runs as the person who made the flow, on the workspace's credits. A run that needs credits it does not have fails at that step with the endpoint's own 402 message.

**The webhook URL** (`trigger.webhook`): `hookUrl` on `GET /v1/flows/{id}`, `https://eroq.ai/hooks/flows/{id}/{token}`. No key: the URL is the key, keep it private (a new `salt` replaces it). POST any JSON, a form, or plain text (up to 64 KB): it is step #1, `{{#1}}` reads its `promptField` (or all of it), `{{#1.field}}` any field. It answers 202 `{ "ok": true, "run_id", "flow_id", "status": "running" }`; 404 for an unknown or replaced URL, 409 `flow_not_published`, 403 `plan_required`, 429 with `Retry-After`, 400 `content_blocked`. A GET only describes the URL, so a link preview never starts a run. `?callback_url=` (or an `X-Eroq-Callback-Url` header) gets the finished run. From Zapier: Webhooks by Zapier › POST; from Make: HTTP › Make a request; from n8n: the HTTP Request node.

## Operations

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/v1/flows` | List the workspace's flows: their triggers, their chain, their last run, the next scheduled run and a run waiting on a post. |
| `POST` | `/v1/flows` | Create a flow from a graph, a template, or blank, starting with the trigger you pick. |
| `GET` | `/v1/flows/{id}` | One flow — its graph, its webhook URL when it has the trigger — and its last 10 runs. |
| `PATCH` | `/v1/flows/{id}` | Rename it, replace its graph, publish it or take it back to a draft. A run underway keeps the graph it started with. |
| `DELETE` | `/v1/flows/{id}` | Delete the flow and its runs — everything it rendered stays in the library. |
| `POST` | `/v1/flows/{id}/runs` | Run it now: 202 with the run, which moves on in the background — poll it. Refused (400) before anything is spent when a step is not set up or an input the trigger asks for is missing. |
| `GET` | `/v1/flows/{id}/runs` | The flow's runs, newest first (the last 30) — `source` says how each started: studio, api, attach, folder, schedule, webhook. |
| `GET` | `/v1/flows/{id}/runs/{run_id}` | Where a run stands, step by step — looking moves it on. `waiting` names the post it waits on. |
| `POST` | `/v1/flows/{id}/runs/{run_id}/stop` | Stop a run: steps not started are skipped, a post it waits on is let go; a render already sent still finishes (and is paid). |

## GET /v1/flows

List the workspace's flows: their triggers, their chain, their last run, the next scheduled run and a run waiting on a post.

### Example request

```bash
curl https://eroq.ai/v1/flows \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/flows', {
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.get("https://eroq.ai/v1/flows", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://eroq.ai/v1/flows", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "object": "list",
  "flows": [{
    "id": "1e45…", "name": "Prompt to Discord",
    "triggers": ["trigger.manual"], "renderKind": null, "published": true, "nextRunAt": null,
    "steps": ["trigger.manual", "generate.video", "send.webhook"],
    "lastRun": { "status": "done", "at": "2026-10-02T14:05:00Z", "source": "api" }, "waiting": null
  }]
}
```

## POST /v1/flows

Create a flow from a graph, a template, or blank, starting with the trigger you pick.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no | Up to 80 characters. |
| `graph` | object | no | `{ nodes, edges }` — see the notes. Sanitized: unknown steps, loops and lines into a trigger are dropped. |
| `template` | string | no | Start from a template instead: `caption-every-clip`, `clip-to-tiktok`, `daily-clip`, `still-to-reel`, `clip-to-discord`, `webhook-to-clip`, `scripted-voiceover`. |
| `trigger` | string | no | Without `graph` or `template`: the trigger a blank flow starts with, `manual` (default), `render`, `schedule` or `webhook`. |
| `tz` | string | no | With `trigger: "schedule"`: the IANA time zone its hour is read in. Default `UTC`. |
| `published` | boolean | no | Put it online at once (its schedule, watched folder, webhook URL and API runs start it). Studio plan or higher. Default `false`: a draft. |

### Example request

```bash
curl https://eroq.ai/v1/flows \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Prompt to Discord",
  "graph": {
    "nodes": [
      {
        "id": "start",
        "n": 1,
        "type": "trigger.manual",
        "x": 80,
        "y": 200,
        "config": {
          "askPrompt": true
        }
      },
      {
        "id": "clip",
        "n": 2,
        "type": "generate.video",
        "x": 300,
        "y": 200,
        "config": {
          "prompt": "{{#1}}",
          "model": "seedance-2-0-mini",
          "seconds": 5,
          "aspect": "9:16"
        }
      },
      {
        "id": "team",
        "n": 3,
        "type": "send.webhook",
        "x": 520,
        "y": 200,
        "config": {
          "url": "https://discord.com/api/webhooks/…",
          "message": "New take: {{#2}}"
        }
      }
    ],
    "edges": [
      {
        "from": "start",
        "to": "clip"
      },
      {
        "from": "clip",
        "to": "team"
      }
    ]
  }
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/flows', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Prompt to Discord",
    "graph": {
      "nodes": [
        {
          "id": "start",
          "n": 1,
          "type": "trigger.manual",
          "x": 80,
          "y": 200,
          "config": {
            "askPrompt": true
          }
        },
        {
          "id": "clip",
          "n": 2,
          "type": "generate.video",
          "x": 300,
          "y": 200,
          "config": {
            "prompt": "{{#1}}",
            "model": "seedance-2-0-mini",
            "seconds": 5,
            "aspect": "9:16"
          }
        },
        {
          "id": "team",
          "n": 3,
          "type": "send.webhook",
          "x": 520,
          "y": 200,
          "config": {
            "url": "https://discord.com/api/webhooks/…",
            "message": "New take: {{#2}}"
          }
        }
      ],
      "edges": [
        {
          "from": "start",
          "to": "clip"
        },
        {
          "from": "clip",
          "to": "team"
        }
      ]
    }
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post(
    "https://eroq.ai/v1/flows",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "name": "Prompt to Discord",
        "graph": {
            "nodes": [
                {
                    "id": "start",
                    "n": 1,
                    "type": "trigger.manual",
                    "x": 80,
                    "y": 200,
                    "config": {
                        "askPrompt": True
                    }
                },
                {
                    "id": "clip",
                    "n": 2,
                    "type": "generate.video",
                    "x": 300,
                    "y": 200,
                    "config": {
                        "prompt": "{{#1}}",
                        "model": "seedance-2-0-mini",
                        "seconds": 5,
                        "aspect": "9:16"
                    }
                },
                {
                    "id": "team",
                    "n": 3,
                    "type": "send.webhook",
                    "x": 520,
                    "y": 200,
                    "config": {
                        "url": "https://discord.com/api/webhooks/…",
                        "message": "New take: {{#2}}"
                    }
                }
            ],
            "edges": [
                {
                    "from": "start",
                    "to": "clip"
                },
                {
                    "from": "clip",
                    "to": "team"
                }
            ]
        }
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "name": "Prompt to Discord",
  "graph": {
    "nodes": [
      {
        "id": "start",
        "n": 1,
        "type": "trigger.manual",
        "x": 80,
        "y": 200,
        "config": {
          "askPrompt": true
        }
      },
      {
        "id": "clip",
        "n": 2,
        "type": "generate.video",
        "x": 300,
        "y": 200,
        "config": {
          "prompt": "{{#1}}",
          "model": "seedance-2-0-mini",
          "seconds": 5,
          "aspect": "9:16"
        }
      },
      {
        "id": "team",
        "n": 3,
        "type": "send.webhook",
        "x": 520,
        "y": 200,
        "config": {
          "url": "https://discord.com/api/webhooks/…",
          "message": "New take: {{#2}}"
        }
      }
    ],
    "edges": [
      {
        "from": "start",
        "to": "clip"
      },
      {
        "from": "clip",
        "to": "team"
      }
    ]
  }
}`)

	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/flows", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "flow": {
    "id": "1e45…", "name": "Prompt to Discord", "published": false, "nextRunAt": null,
    "graph": { "nodes": [{ "id": "start", "n": 1, "type": "trigger.manual", "config": { "askPrompt": true, "askImage": false } }, …], "edges": [{ "from": "start", "to": "clip" }, …] },
    "createdAt": "2026-10-02T14:02:11Z", "updatedAt": "2026-10-02T14:02:11Z"
  }
}
```

## GET /v1/flows/{id}

One flow — its graph, its webhook URL when it has the trigger — and its last 10 runs.

### Example request

```bash
curl https://eroq.ai/v1/flows/FLOW_ID \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/flows/FLOW_ID', {
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.get("https://eroq.ai/v1/flows/FLOW_ID", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://eroq.ai/v1/flows/FLOW_ID", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "flow": { "id": "1e45…", "name": "Prompt to Discord", "published": true, "nextRunAt": null, "graph": { … }, "hookUrl": "https://eroq.ai/hooks/flows/1e45…/Qm9w…" },
  "runs": [{ "id": "e285…", "status": "done", "source": "api", "credits": 30, "startedAt": "2026-10-02T14:05:00Z" }]
}
```

## PATCH /v1/flows/{id}

Rename it, replace its graph, publish it or take it back to a draft. A run underway keeps the graph it started with.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no | New name, up to 80 characters. |
| `graph` | object | no | The whole graph, replaced (sanitized). Omit to keep it. |
| `published` | boolean | no | Online (`true`, Studio plan or higher) or a draft (`false`, never refused). |

### Example request

```bash
curl -X PATCH https://eroq.ai/v1/flows/FLOW_ID \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Prompt to Discord, vertical",
  "published": true
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/flows/FLOW_ID', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Prompt to Discord, vertical",
    "published": true
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.patch(
    "https://eroq.ai/v1/flows/FLOW_ID",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "name": "Prompt to Discord, vertical",
        "published": True
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "name": "Prompt to Discord, vertical",
  "published": true
}`)

	req, _ := http.NewRequest("PATCH", "https://eroq.ai/v1/flows/FLOW_ID", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{ "flow": { "id": "1e45…", "name": "Prompt to Discord, vertical", "published": true, "nextRunAt": null, "graph": { … }, "hookUrl": null } }
```

## DELETE /v1/flows/{id}

Delete the flow and its runs — everything it rendered stays in the library.

### Example request

```bash
curl -X DELETE https://eroq.ai/v1/flows/FLOW_ID \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/flows/FLOW_ID', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.delete("https://eroq.ai/v1/flows/FLOW_ID", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("DELETE", "https://eroq.ai/v1/flows/FLOW_ID", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{ "ok": true, "id": "1e45…" }
```

## POST /v1/flows/{id}/runs

Run it now: 202 with the run, which moves on in the background — poll it. Refused (400) before anything is spent when a step is not set up or an input the trigger asks for is missing.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `trigger` | string | no | Where it starts: `manual` (default), `render` (needs `inputs.creation_id`), `schedule` (try a scheduled flow now), `webhook` (with `inputs.payload`). |
| `inputs` | object | no | `prompt` (asked by `askPrompt`, read as `{{#1}}`), `creation_id` (a creation of yours: the render to run on, or the picture `askImage` wants), `image_url` (an https picture, for `askImage`), `payload` (the body a « When a webhook is called » trigger reads, as if its URL had been called), `locale` (the language Clap writes in). |
| `callback_url` | string | no | An https URL that gets the finished run, once, as a `flow.run.*` event body (Zapier callbacks, n8n's Wait node « On webhook call »). |

### Example request

```bash
curl https://eroq.ai/v1/flows/FLOW_ID/runs \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "inputs": {
    "prompt": "A sunrise over a quiet harbor, slow push-in"
  }
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/flows/FLOW_ID/runs', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "inputs": {
      "prompt": "A sunrise over a quiet harbor, slow push-in"
    }
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post(
    "https://eroq.ai/v1/flows/FLOW_ID/runs",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "inputs": {
            "prompt": "A sunrise over a quiet harbor, slow push-in"
        }
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "inputs": {
    "prompt": "A sunrise over a quiet harbor, slow push-in"
  }
}`)

	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/flows/FLOW_ID/runs", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "run": {
    "id": "e285…", "flowId": "1e45…", "status": "running", "source": "api", "startNode": "start",
    "inputs": { "prompt": "A sunrise over a quiet harbor, slow push-in" },
    "results": { "start": { "status": "done", "output": "text", "text": "A sunrise over a quiet harbor, slow push-in" }, "clip": { "status": "pending" }, "team": { "status": "pending" } },
    "credits": 0, "waiting": null, "startedAt": "2026-10-02T14:05:00Z", "finishedAt": null
  },
  "poll": "/v1/flows/1e45…/runs/e285…"
}
```

## GET /v1/flows/{id}/runs

The flow's runs, newest first (the last 30) — `source` says how each started: studio, api, attach, folder, schedule, webhook.

### Example request

```bash
curl https://eroq.ai/v1/flows/FLOW_ID/runs \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/flows/FLOW_ID/runs', {
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.get("https://eroq.ai/v1/flows/FLOW_ID/runs", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://eroq.ai/v1/flows/FLOW_ID/runs", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "object": "list",
  "runs": [{ "id": "e285…", "status": "done", "source": "attach", "credits": 0, "startedAt": "2026-10-02T14:05:00Z", "finishedAt": "2026-10-02T14:05:09Z" }]
}
```

## GET /v1/flows/{id}/runs/{run_id}

Where a run stands, step by step — looking moves it on. `waiting` names the post it waits on.

### Example request

```bash
curl https://eroq.ai/v1/flows/FLOW_ID/runs/{run_id} \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/flows/FLOW_ID/runs/{run_id}', {
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.get("https://eroq.ai/v1/flows/FLOW_ID/runs/{run_id}", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://eroq.ai/v1/flows/FLOW_ID/runs/{run_id}", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "run": {
    "id": "e285…", "status": "done", "source": "api", "credits": 30,
    "results": {
      "start": { "status": "done", "output": "text", "text": "A sunrise over a quiet harbor, slow push-in" },
      "clip": { "status": "done", "output": "video", "creationId": "4a17…", "url": "https://cdn.eroq.ai/…/clip.mp4?token=…", "credits": 30 },
      "team": { "status": "done", "text": "HTTP 204" }
    },
    "waiting": null, "startedAt": "2026-10-02T14:05:00Z", "finishedAt": "2026-10-02T14:06:41Z"
  }
}
```

## POST /v1/flows/{id}/runs/{run_id}/stop

Stop a run: steps not started are skipped, a post it waits on is let go; a render already sent still finishes (and is paid).

### Example request

```bash
curl -X POST https://eroq.ai/v1/flows/FLOW_ID/runs/{run_id}/stop \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/flows/FLOW_ID/runs/{run_id}/stop', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post("https://eroq.ai/v1/flows/FLOW_ID/runs/{run_id}/stop", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/flows/FLOW_ID/runs/{run_id}/stop", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{ "run": { "id": "e285…", "status": "stopped", "results": { … } } }
```

---
Canonical: https://eroq.ai/docs/flows · Index for agents: https://eroq.ai/llms.txt · OpenAPI: https://eroq.ai/openapi.json
