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

# Workflows

> Create and manage automated block-based workflows with visual graph definitions.

Workflows are DAG-based automation pipelines that execute a fixed sequence of blocks (LLM calls, code execution, conditions, agent runs). Unlike agents that decide what to do dynamically, workflows follow a predetermined graph. They can be executed directly, streamed, or triggered externally via webhooks.

## Common Patterns

Create a workflow, define its graph with PUT /api/workflows/\{id}/graph, then execute. For external triggers, deploy the workflow and arm it to get a webhook URL. Webhooks are single-use, so re-arm after each trigger. Use the streaming endpoint for real-time block execution updates.

## CRUD

### GET /api/workflows

List workflows.

<ParamField query="limit" type="integer">
  Max results
</ParamField>

<ParamField query="offset" type="integer">
  Pagination offset
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  requests.get(f"{BASE_URL}/api/workflows", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/workflows`, { headers });
  ```

  ```bash cURL theme={null}
  curl '{BASE_URL}/api/workflows' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

### POST /api/workflows

Create a workflow.

<RequestExample>
  ```json Request theme={null}
  { "name": "My Workflow" }
  ```
</RequestExample>

<CodeGroup>
  ```python Python theme={null}
  requests.post(f"{BASE_URL}/api/workflows", headers=headers, json={"name": "My Workflow"})
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/workflows`, { method: "POST", headers, body: JSON.stringify({ name: "My Workflow" }) });
  ```

  ```bash cURL theme={null}
  curl -X POST '{BASE_URL}/api/workflows' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"name": "My Workflow"}'
  ```
</CodeGroup>

### GET /api/workflows/{id}

Get workflow with blocks and edges.

<ParamField path="id" type="string" required>
  Workflow ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  requests.get(f"{BASE_URL}/api/workflows/{wf_id}", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/workflows/${wfId}`, { headers });
  ```

  ```bash cURL theme={null}
  curl '{BASE_URL}/api/workflows/{id}' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

### PATCH /api/workflows/{id}

Update workflow metadata.

<ParamField path="id" type="string" required>
  Workflow ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  requests.patch(f"{BASE_URL}/api/workflows/{wf_id}", headers=headers, json={"name": "Renamed"})
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/workflows/${wfId}`, { method: "PATCH", headers, body: JSON.stringify({ name: "Renamed" }) });
  ```

  ```bash cURL theme={null}
  curl -X PATCH '{BASE_URL}/api/workflows/{id}' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"name": "Renamed"}'
  ```
</CodeGroup>

### DELETE /api/workflows/{id}

Delete a workflow.

<ParamField path="id" type="string" required>
  Workflow ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  requests.delete(f"{BASE_URL}/api/workflows/{wf_id}", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/workflows/${wfId}`, { method: "DELETE", headers });
  ```

  ```bash cURL theme={null}
  curl -X DELETE '{BASE_URL}/api/workflows/{id}' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

## Graph

### PUT /api/workflows/{id}/graph

Save the complete graph: blocks and edges.

<ParamField path="id" type="string" required>
  Workflow ID
</ParamField>

<RequestExample>
  ```json Request theme={null}
  {
    "blocks": [
      { "id": "start", "type": "starter", "config": {}, "position": {"x": 0, "y": 0} },
      { "id": "out", "type": "response", "config": {}, "position": {"x": 300, "y": 0} }
    ],
    "edges": [
      { "source": "start", "target": "out" }
    ]
  }
  ```

  Unknown block types are rejected with `400 Unknown block type`. Canonical types: `starter`, `webhook`, `agent`, `orchestration`, `code`, `condition`, `general_api`, `platform_api`, `response`, `split` (plus back-compat aliases `function` → code, `api_call` → general\_api).
</RequestExample>

<CodeGroup>
  ```python Python theme={null}
  requests.put(f"{BASE_URL}/api/workflows/{wf_id}/graph", headers=headers, json={"blocks": [...], "edges": [...]})
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/workflows/${wfId}/graph`, { method: "PUT", headers, body: JSON.stringify({ blocks: [...], edges: [...] }) });
  ```

  ```bash cURL theme={null}
  curl -X PUT '{BASE_URL}/api/workflows/{id}/graph' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"blocks": [], "edges": []}'
  ```
</CodeGroup>

## Deploy

### POST /api/workflows/{id}/deploy

Deploy the workflow (enables webhook triggering).

<ParamField path="id" type="string" required>
  Workflow ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  requests.post(f"{BASE_URL}/api/workflows/{wf_id}/deploy", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/workflows/${wfId}/deploy`, { method: "POST", headers });
  ```

  ```bash cURL theme={null}
  curl -X POST '{BASE_URL}/api/workflows/{id}/deploy' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

### POST /api/workflows/{id}/undeploy

Undeploy the workflow.

<ParamField path="id" type="string" required>
  Workflow ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  requests.post(f"{BASE_URL}/api/workflows/{wf_id}/undeploy", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/workflows/${wfId}/undeploy`, { method: "POST", headers });
  ```

  ```bash cURL theme={null}
  curl -X POST '{BASE_URL}/api/workflows/{id}/undeploy' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

### POST /api/workflows/{id}/arm

Arm the workflow's webhook for a single external trigger. Opens a 10-minute window during which the webhook accepts exactly one call; after it fires or expires, re-arm.

<ParamField path="id" type="string" required>
  Workflow ID
</ParamField>

<Note>
  The response is `{"ok": true, "armed_until": "<iso8601>"}`, **not** a webhook id/secret. The `webhook_id` and `webhook_secret` live on the webhook block's config in the saved graph; fetch them with `GET /api/workflows/{id}` and read from the block whose `type == "webhook"`.
</Note>

<CodeGroup>
  ```python Python theme={null}
  response = requests.post(f"{BASE_URL}/api/workflows/{wf_id}/arm", headers=headers)
  print(response.json())  # {"ok": true, "armed_until": "2026-01-01T00:00:00Z"}
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(`${BASE_URL}/api/workflows/${wfId}/arm`, { method: "POST", headers });
  const { ok, armed_until } = await res.json();
  ```

  ```bash cURL theme={null}
  curl -X POST '{BASE_URL}/api/workflows/{id}/arm' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

## Execution

### POST /api/workflows/{id}/execute

Execute the workflow synchronously.

<ParamField path="id" type="string" required>
  Workflow ID
</ParamField>

<ParamField body="variables" type="object">
  Map of input variable names → values. Defaults to `{}` when omitted.
</ParamField>

<Note>
  `variables` is the canonical key. The route also accepts `input` as a legacy alias (`data.get("variables", data.get("input", {}))`); prefer `variables` in new code.
</Note>

<RequestExample>
  ```json Request theme={null}
  { "variables": { "text": "..." } }
  ```
</RequestExample>

<CodeGroup>
  ```python Python theme={null}
  requests.post(f"{BASE_URL}/api/workflows/{wf_id}/execute", headers=headers, json={"variables": {"text": "..."}})
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/workflows/${wfId}/execute`, { method: "POST", headers, body: JSON.stringify({ variables: { text: "..." } }) });
  ```

  ```bash cURL theme={null}
  curl -X POST '{BASE_URL}/api/workflows/{id}/execute' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"variables": {"text": "..."}}'
  ```
</CodeGroup>

### POST /api/workflows/{id}/execute/stream

Execute with streaming SSE. Same body shape as `/execute`: pass `variables` (canonical) or `input` (legacy alias).

<ParamField path="id" type="string" required>
  Workflow ID
</ParamField>

<ParamField body="variables" type="object">
  Map of input variable names → values. Defaults to `{}` when omitted.
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  requests.post(f"{BASE_URL}/api/workflows/{wf_id}/execute/stream", headers=headers, json={"variables": {"text": "..."}}, stream=True)
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/workflows/${wfId}/execute/stream`, { method: "POST", headers, body: JSON.stringify({ variables: { text: "..." } }) });
  ```

  ```bash cURL theme={null}
  curl -N -X POST '{BASE_URL}/api/workflows/{id}/execute/stream' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"variables": {"text": "..."}}'
  ```
</CodeGroup>

### GET /api/workflows/{id}/executions

List execution history.

<ParamField path="id" type="string" required>
  Workflow ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  requests.get(f"{BASE_URL}/api/workflows/{wf_id}/executions", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/workflows/${wfId}/executions`, { headers });
  ```

  ```bash cURL theme={null}
  curl '{BASE_URL}/api/workflows/{id}/executions' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

### GET /api/workflows/{id}/executions/{eid}/logs

Get per-block execution logs.

<ParamField path="id" type="string" required>
  Workflow ID
</ParamField>

<ParamField path="eid" type="string" required>
  Execution ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  requests.get(f"{BASE_URL}/api/workflows/{wf_id}/executions/{exec_id}/logs", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/workflows/${wfId}/executions/${execId}/logs`, { headers });
  ```

  ```bash cURL theme={null}
  curl '{BASE_URL}/api/workflows/{id}/executions/{eid}/logs' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

## Error Responses

Workflow routes return one of two body shapes:

* Plain `{"error": "<message>"}`: list/create/patch/delete and the deploy/undeploy/arm endpoints.
* Structured `{"error": "<message>", "error_code": "<UPPER_SNAKE_CODE>"}` (via the shared `error_response` helper): `GET /workflows/{id}`, `PUT /workflows/{id}/graph`, and the synchronous `/execute` endpoint.

Streaming endpoints (`/execute/stream`) return HTTP 200 and emit errors as SSE `data: {"type": "error", ...}` events; only the timeout event includes `error_code` (`EXECUTION_TIMEOUT`).

| Status | `error_code`         | Description                                                                                                                                                                |
| ------ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | —                    | Missing required field (e.g. `name`, no fields to update, no JSON body)                                                                                                    |
| 400    | `VALIDATION_ERROR`   | Graph save: each block needs `id`/`type`, each edge needs `source`/`target`, block types must be in the engine registry, and edges must reference blocks in the same graph |
| 404    | `WORKFLOW_NOT_FOUND` | No workflow exists with the given ID (`GET /workflows/{id}`, `PUT /graph`, `POST /execute`)                                                                                |
| 404    | —                    | No workflow or execution exists with the given ID (other endpoints)                                                                                                        |
| 504    | `EXECUTION_TIMEOUT`  | Synchronous `/execute` exceeded its timeout                                                                                                                                |
| 500    | `EXECUTION_FAILED`   | Synchronous `/execute` raised an error                                                                                                                                     |
