{"openapi":"3.0.3","info":{"title":"Floqer Public API","description":"Programmatic access to Floqer workflows and actions.\n\n## Recommended starting point for AI agents\n\nIf you're building workflows with this API, the easiest entry point is [`/docs/llms-full.txt`](/docs/llms-full.txt) — a single flattened text file that bundles Floqer's domain concepts (workflows, sheets, the action chain, reference tokens, connections), the full action catalog with configuration shapes and dynamic-options bindings, the API guide, and this OpenAPI spec.\n\n```\nGET /docs/llms-full.txt\n```\n\nLoading it once gives you the context you'd otherwise stitch together from many endpoints — how `{{input.X}}` and `{{<action_instance_id>.X}}` reference tokens flow through `inputs` and `outputs`, which fields on which actions are dynamic (and how to fetch their option lists), and the typical order operations happen in (Create Workflow → Add Inputs → Add Action → Get Options → Configure Action → Add Rows → Run). Use this OpenAPI for endpoint-level schema details once you have the bigger picture.\n\n---\n\n## Authentication\nAll endpoints require a Bearer token (API key starting with `floq_`). Pass it in the `Authorization` header:\n```\nAuthorization: Bearer floq_your_api_key\n```\n\n## Rate Limits\nDefault: **60 requests/minute**, **10,000 requests/day** per API key. Rate limit headers are included in every response:\n\n| Header | Description |\n|--------|-------------|\n| `X-RateLimit-Limit` | Max requests allowed in the current window |\n| `X-RateLimit-Remaining` | Requests remaining in the current window |\n| `X-RateLimit-Reset` | Unix timestamp when the window resets |\n| `Retry-After` | Seconds to wait (only on 429 responses) |\n\n## Response Envelope\nAll successful responses are wrapped in a standard envelope:\n```json\n{\n  \"status\": 200,\n  \"data\": { ... },\n  \"message\": \"Optional success message\"\n}\n```\n\n## Errors\nError responses use a consistent envelope:\n```json\n{\n  \"status\": 400,\n  \"error\": \"Bad Request\",\n  \"message\": \"Descriptive error message\"\n}\n```\n\n| Status | Meaning |\n|--------|---------|\n| `400` | Bad Request — invalid parameters or missing required fields |\n| `401` | Unauthorized — missing or invalid API key |\n| `403` | Forbidden — API key lacks the required scope or access |\n| `404` | Not Found — resource does not exist |\n| `429` | Too Many Requests — rate limit exceeded |\n| `500` | Internal Server Error — unexpected failure |\n\n## Pagination\nList endpoints use cursor-based pagination with `pageSize` (default: 20) and `pageNo` (1-indexed). The response includes `totalCount`.\n\n## Scopes\nAPI keys are issued with permission scopes that control access:\n\n| Scope | Description |\n|-------|-------------|\n| `workflows:read` | List and view workflows |\n| `workflows:write` | Create, update, delete, and configure workflows |\n| `workflows:run` | Run and stop workflows |\n| `workflows:data:read` | Read workflow data rows and run history |\n| `workflows:data:write` | Add and delete workflow data rows |\n| `actions:read` | List available actions and view action configs |\n| `actions:write` | Add, save, and configure actions in workflows |\n\n---\n\n## Quick Start — Building a Workflow via API\n\n### 1. Create a Workflow\n`POST /api/v1/workflows` with `{\"name\": \"My Pipeline\"}`\n\n### 2. Define Input Columns\n`PUT /api/v1/workflows/{id}/inputs` — define the input fields (e.g. linkedin_url, email). Each input returns a `responseId` used to wire up actions.\n\n### 3. Add Actions\n`POST /api/v1/actions/workflows/{id}/add` — add actions by `actionName` (find valid names via `GET /api/v1/actions`). Actions are auto-configured with default waterfall providers and linked into the action chain.\n\n### 4. Configure Action Inputs\n`PATCH /api/v1/actions/workflows/{id}/{actionId}` — map variables from previous actions. Use `{{responseId}}` (bare responseId) in `inputString` and include `tiptapJson` for frontend rendering. **Important:** Both `inputString` and tiptapJson customTag `id` use the bare `responseId`.\n\n### 5. Add Data Rows\n`POST /api/v1/workflows/{id}/data` — insert rows into the workflow's data table. Use the **field names** you defined in step 2 (not responseIds). Returns `dataIds` (UUIDs) for each inserted row.\n\n### 6. Run the Workflow\n`POST /api/v1/workflows/{id}/run` — execute the workflow on your data. Pass the `dataIds` returned from step 5 to run those specific rows from the beginning:\n```json\n{\"dataIds\": [\"a1b2c3d4-...\", \"e5f6g7h8-...\"]}\n```\n\n### Variable Reference Cheat Sheet\n| Field | Format | Example |\n|-------|--------|---------|\n| `inputString` | `{{responseId}}` | `{{b2b9591a-...}}` |\n| tiptapJson customTag `id` | bare responseId | `b2b9591a-...` |\n| tiptapJson customTag `parent` | action display name | `Input` |\n| tiptapJson customTag `label` | field name | `linkedin_url` |\n\n---\n\n## Working with Sheets\n\nSheets are child workflows linked to a parent. Each sheet has its own data, actions, and runs — manage it using any workflow endpoint with the sheet's own ID.\n\n### Create a Sheet\n`POST /api/v1/workflows` with `{\"name\": \"Sheet Name\", \"parentWorkflowId\": \"{parentId}\"}`\n\nReturns a `workflowId` for the new sheet. Use this ID with any workflow endpoint (configure inputs, add data, run, etc.).\n\n### List Sheets\n`GET /api/v1/workflows/{parentId}/sheets` — returns all child sheets and their `sheetsConfig` (includes display order).\n\n### Reorder Sheets\n`PUT /api/v1/workflows/{parentId}/sheets/order` — pass `sheetsOrder` (array of sheet IDs) to set the display order.\n\n### Key Points\n- A sheet is just a workflow — use `GET`, `PUT`, `DELETE /workflows/{sheetId}` to view, update, or delete it.\n- Duplicating a sheet (`POST /workflows/{sheetId}/duplicate`) auto-links the copy to the same parent.\n- Data is **not** copied during duplication — only the action configuration.\n- Deleting a sheet does not affect the parent or other sheets.","version":"1.0.0","contact":{"name":"Floqer Support","url":"https://floqer.com"}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key in the format: floq_..."}},"schemas":{"SuccessMessage":{"type":"object","description":"Simple success response with a confirmation message.","required":["message"],"properties":{"status":{"type":"integer","description":"HTTP status code","example":200},"message":{"type":"string","description":"Human-readable success message","example":"Workflow updated successfully"}}},"CreateWorkflowBody":{"type":"object","description":"Body for Create Workflow. Only `name` is required.","properties":{"name":{"type":"string","description":"Workflow name. Not required to be unique in the organization.","example":"Lead Enrichment Pipeline"}},"required":["name"]},"Workflow":{"type":"object","description":"A workflow summary. Timestamps are ISO 8601 UTC; `last_run_at` is `null` until the first run.","properties":{"workflow_id":{"type":"string","format":"uuid","description":"UUID of the workflow. Use this with every endpoint that takes `{workflow_id}`."},"name":{"type":"string","description":"Workflow name as set at creation."},"created_at":{"type":"string","format":"date-time","description":"UTC timestamp (ISO 8601) when the workflow was created."},"updated_at":{"type":"string","format":"date-time","description":"UTC timestamp (ISO 8601) of the most recent configuration change (name, inputs, actions, sheet settings)."},"last_run_at":{"type":["null","string"],"format":"date-time","description":"UTC timestamp (ISO 8601) of the most recent run queued on any sheet in this workflow. `null` if the workflow has never been run."}},"required":["workflow_id","name","created_at","updated_at","last_run_at"]},"WorkflowDeleted":{"type":"object","description":"Confirmation envelope for a deleted workflow.","properties":{"workflow_id":{"type":"string","format":"uuid","description":"UUID of the workflow that was deleted."},"deleted":{"type":"boolean","description":"Always `true` on a 200 response. Included so agents can branch on a single field."}},"required":["workflow_id","deleted"]},"Sheet":{"type":"object","description":"A sheet in a workflow. Pass `sheet_id` as the `{sheet_id}` path parameter on every sheet-scoped endpoint.","properties":{"sheet_id":{"type":"string","format":"uuid","description":"UUID of the sheet. Pass as `{sheet_id}` on every sheet-scoped endpoint, and as `target_sheet_id` when another sheet routes data here via `send_to_sheet`.","example":"e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b"},"workflow_id":{"type":"string","format":"uuid","description":"UUID of the parent workflow this sheet belongs to.","example":"531952c2-3d07-4be2-8c4b-733acba3187b"},"name":{"type":"string","description":"Sheet name as set at creation. Not required to be unique within a workflow.","example":"Employees"},"is_main_sheet":{"type":"boolean","description":"`true` for the workflow's main sheet — auto-created with the workflow, always present, cannot be deleted separately, and has `sheet_id === workflow_id`. `false` for additional sheets added via Create Sheet."},"auto_run":{"type":"boolean","description":"When `true` (default), the sheet's action chain runs automatically whenever new data rows arrive (via Add Data Rows or via a `send_to_sheet` action on another sheet). When `false`, runs must be triggered explicitly with Run Rows."},"cache_enabled":{"type":"boolean","description":"When `true` (default), a row with input column values identical to a previously-run row reuses the previous row's outputs instead of re-running the action chain. Matching compares every input column value on the row as a string, after variable references are resolved."},"cache_since":{"type":["null","string"],"format":"date","description":"Earliest date (`YYYY-MM-DD`, UTC) from which cached runs are considered fresh. Runs performed on or after this date are reusable; runs performed before are ignored even when inputs match. Ignored when `cache_enabled` is `false`. `null` means all cached runs are considered fresh regardless of age.","example":"2026-03-17"}},"required":["sheet_id","workflow_id","name","is_main_sheet","auto_run","cache_enabled","cache_since"]},"CreateSheetBody":{"type":"object","description":"Body for Create Sheet. Only `name` is required.","properties":{"name":{"type":"string","description":"Sheet name. Not required to be unique within a workflow.","example":"Employees"},"auto_run":{"type":"boolean","description":"When `true` (default), the sheet's action chain runs automatically whenever new data rows arrive — via Add Data Rows or via a `send_to_sheet` action on another sheet. When `false`, runs must be triggered explicitly with Run Rows."},"cache_enabled":{"type":"boolean","description":"When `true` (default), a row with input column values identical to a previously-run row reuses the previous row's outputs instead of re-running the action chain. Set to `false` to always re-run."},"cache_since":{"type":["string","null"],"format":"date","description":"Earliest date (`YYYY-MM-DD`, UTC) from which cached runs are considered fresh. Ignored when `cache_enabled` is `false`. Default: `null` — all cached runs are considered fresh regardless of age.","example":"2026-03-17"}},"required":["name"]},"Input":{"type":"object","description":"A single configured input field on a workflow sheet. Each input becomes a column in the sheet's data table and gets a `reference` token that downstream actions can use to pull the value.","properties":{"name":{"type":"string","description":"Input field name. Unique within the sheet. Used to build the `reference` token.","example":"linkedin_url"},"type":{"type":"string","enum":["string","url","email","number"],"description":"Input data type. One of: `string`, `url`, `email`, `number`.","example":"url"},"description":{"type":"string","description":"Human-readable description of the input. Empty string when not set.","example":"LinkedIn company profile URL"},"reference":{"type":"string","description":"Variable reference token. Use this value in downstream action `inputString`s to pull the input's value at run time. Always of the form `{{input.<name>}}`.","example":"{{input.linkedin_url}}"}},"required":["name","type","description","reference"]},"InputCreate":{"type":"object","description":"An input field to add to a workflow sheet. `reference` is server-computed from `name` and is never accepted on the request.","properties":{"name":{"type":"string","description":"Field name — used as the column header and as the key when adding data rows. Must be unique within the sheet.","example":"linkedin_url"},"type":{"type":"string","enum":["string","url","email","number"],"description":"Data type. Helps agents match fields to action inputs (e.g. an action that needs a URL can search for `type: url`). One of: `string`, `url`, `email`, `number`.","example":"url"},"description":{"type":"string","description":"Human-readable description of what this field contains.","example":"LinkedIn company profile URL"}},"required":["name","type"]},"ActionInput":{"type":"object","description":"A single input field on an action instance. The caller wires this field up via the Configure Action endpoint.","properties":{"name":{"type":"string","description":"Input field identifier (the template's `payloadStructureId`, lowercased).","example":"linkedin_url"},"type":{"type":"string","description":"Field type (e.g. `string`, `url`, `email`, `number`, `boolean`, `promptField`, `stepDownSearch`).","example":"url"},"required":{"type":"boolean","description":"Whether this field must be configured before the action can run."},"description":{"type":"string","description":"Human-readable description of what this field accepts.","example":"LinkedIn profile URL of the person"}},"required":["name","type","required"]},"ActionOutput":{"type":"object","description":"A single output field produced by an action instance when it runs. `reference` is the variable token downstream actions use to pull this value. Outputs of type `structured_array` (a list of structured rows) additionally surface their per-column schema under `columns`, each with its own reference token for addressing individual columns.","properties":{"name":{"type":"string","description":"Output field identifier (snake_case).","example":"full_name"},"type":{"type":"string","description":"Output data type (e.g. `string`, `url`, `number`, `raw_array`, `json`, `structured_array`). `structured_array` is a list of structured rows; the per-row schema is exposed via `columns`.","example":"string"},"reference":{"type":"string","description":"Variable reference token. Drop this verbatim into any downstream action's `inputString` (via Configure Action) to pull this output's value at run time. Shape: `{{<action_instance_id>.<name>}}`. For an individual column of a `structured_array`, use the 3-segment form `{{<action_instance_id>.<list>.<column>}}` (also exposed on each entry of `columns[].structured_array_reference`).","example":"{{enrich_person_linkedin_profile_1.full_name}}"},"description":{"type":"string","description":"Human-readable description of this output.","example":"Work history as JSON. Each item: title, company, start_date, end_date, summary, url, company_domain, company_identifier"},"columns":{"type":"array","description":"Present only on `structured_array` outputs. One entry per column inside the list, with its own `structured_array_reference` token shaped `{{<action_instance_id>.<list>.<column>}}`. Empty / absent for outputs whose columns are user-defined and not yet configured (e.g. a freshly added `raw_to_structured_array` action).","items":{"type":"object","properties":{"name":{"type":"string","description":"Column identifier (snake_case)."},"type":{"type":"string","description":"Column data type."},"structured_array_reference":{"type":"string","description":"3-segment reference token for this column inside the parent `structured_array`. Shape: `{{<action_instance_id>.<list>.<column>}}`.","example":"{{raw_to_structured_array_1.list.first_name}}"}},"required":["name","type","structured_array_reference"]}}},"required":["name","type","reference"]},"ActionInstance":{"type":"object","description":"An action instance in a workflow sheet's chain. Returned from Add Action and Get Action; contains enough information to subsequently call Configure Action (to wire inputs) or to wire downstream actions to this one (via `outputs[].reference`).","properties":{"action_instance_id":{"type":"string","description":"Unique ID of this action instance within the sheet. Use this value as the `after` param when inserting further actions, and as the path param on Configure / Delete endpoints.","example":"scrape_person_linkedin_profile_1"},"action_id":{"type":"string","description":"Action template identifier this instance was created from.","example":"scrape_person_linkedin_profile"},"display_name":{"type":"string","description":"User-facing name. Defaults to the action template's name; can be overridden per instance in the future.","example":"Scrape Person LinkedIn Profile"},"inputs":{"type":"array","items":{"$ref":"#/components/schemas/ActionInput"},"description":"Input fields the caller must configure via Configure Action."},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/ActionOutput"},"description":"Output fields this action produces when it runs. Each has a `reference` token for downstream wiring."}},"required":["action_instance_id","action_id","display_name","inputs","outputs"]},"AddActionBody":{"type":"object","description":"Body for Add Action. `action_id` is the template; `after` (optional) is an `action_instance_id`; `name` (optional) overrides the template's default display name.","properties":{"action_id":{"type":"string","description":"Action template identifier from the action catalog (e.g. `enrich_company_linkedin_profile`).","example":"enrich_company_linkedin_profile"},"after":{"type":"string","description":"Insert after this `action_instance_id`. Omit to append to the end of the chain. NOTE: this is an instance ID, not an `action_id`.","example":"enrich_company_linkedin_profile_1"},"name":{"type":"string","description":"Optional display name for the new action instance. Whitespace-only values are ignored.","example":"Enrich primary contact"}},"required":["action_id"]},"ConfigureActionBody":{"type":"object","description":"Body for Configure Action. Send only the fields you want to set — unset fields keep their current value.","properties":{"inputs":{"type":"object","description":"Map of field name → value. Each entry's value is one of:\n- **string** — a literal, a `{{public.ref}}`, or a mix of both.\n- **boolean** — for radio / toggle fields.\n- **string[]** — used by two field types: waterfall providers (an ordered list of provider IDs, when the template field's `type === \"stepDownSearch\"`) and multi-dropdown / multi-select fields. The handler picks the right storage shape based on the action template's field `type`.\n- **Array of objects** — for `jsonArray` fields (e.g. Salesforce field/value pairs, Google Sheets column mappings). Each item is typically `{ name, value }`, where `value` can carry `{{public.ref}}` tokens. Extra keys per item are preserved verbatim. Reference translation runs on every string property — if you nest `{{public.refs}}` inside `value`, `prompt`, or any other string field on the item, they get translated independently.\n- **Array of condition groups** — for `path_filter` fields (filter action's `path_conditions`). Each group is `{ conditions: [{variable, operator, values?, combinator?}], combinator? }`. `variable` is a public reference token; `operator` is the comparison operator; `combinator` (AND/OR) joins the previous group at the group level and the previous leaf at the leaf level. See `/docs/action-detail/filter.txt` for the full body shape.\n\n**`label` defaulting on `jsonArray` items.** When an item carries a `name` but no `label`, the server stores `label: <name>` automatically — matches the `{ name, label, value }` triple the runtime engine consumes. Caller-supplied `label` values are always preserved, never overwritten.\n\n**Schema validation note.** This map's value type is intentionally permissive (`additionalProperties: true`) — Fastify's Ajv strict mode reliably rejects nested `anyOf` array-of-object schemas under `additionalProperties` (it returns a confusing \"must be array\" error against values that *are* arrays). The five shapes above are enforced at the handler instead, which inspects each value's runtime type and routes it through the right storage branch.","additionalProperties":true,"example":{"linkedin_url":"{{input.linkedin_url}}","prompt":"Research {{input.company_name}} at {{enrich_company_linkedin_profile_1.website}}"}},"run_if":{"type":["object","null"],"description":"Optional gate deciding whether this action runs for a given row. The action runs only when the resolved value of `variable` satisfies `operator` against `values`. Pass `null` to clear an existing condition. Omit to leave the existing condition unchanged.\n\n**Supported `operator` values depend on the variable's stored type** — the type is derived from the sheet's chain, so you don't pass it.\n\n- **Universal** (any type): `is`, `is not`, `is empty`, `is not empty`. `is empty` / `is not empty` ignore `values`.\n- **String / email / url / imageUrl**: `contains`, `does not contain`, `starts with`, `does not start with`, `ends with`, `does not end with`. Matching is case-insensitive.\n- **Number / currency**: `greater than`, `less than`, `greater than or equal to`, `less than or equal to`, `is between` (pass `[min, max]`).\n- **Date**: `is after`, `is before`, `is between` (pass `[start, end]`). Values are parsed by `moment(...)`.\n- **Array / structured_array / json**: `contains`, `does not contain`.\n\nPass `values` as an array of strings — number / date matchers coerce as needed.","properties":{"variable":{"type":"string","description":"Reference token whose resolved value gets compared. Use `{{input.<field_name>}}` for a sheet input, or `{{<action_instance_id>.<field_name>}}` for an upstream action's output.","examples":["{{input.country}}","{{enrich_company_linkedin_profile_1.employee_count}}"]},"operator":{"type":"string","description":"Comparison operator. See the per-type list above.","examples":["is","greater than","is not empty","contains"]},"values":{"type":"array","description":"Values to compare against. Required for most operators; ignored by `is empty` / `is not empty`. Each item is a literal — variable references inside `values` are not resolved. For `is between`, pass `[low, high]`.","items":{},"examples":[["US","CA"],["100"],["2025-01-01","2025-12-31"]]},"continue_workflow_if_run_condition_not_met":{"type":"boolean","description":"When `true`, downstream actions still run for a row even when this action's condition isn't met (the action itself is skipped). When `false` (default), the row stops at this action when the condition isn't met."}},"required":["variable","operator"]},"continue_workflow_if_action_fails":{"type":"boolean","description":"When `true`, downstream actions still run for a row even if this action fails. When `false` (default), the row stops at this action on failure and downstream cells stay queued."},"note":{"type":"string","description":"Optional free-text note to save on the action. Same upsert behaviour as Save Action Note — overwrites any existing note. Whitespace-only values are ignored.","example":"Skip until Phase 2 — vendor still negotiating contract terms."}},"example":{"inputs":{"linkedin_url":"{{input.linkedin_url}}"},"run_if":{"variable":"{{input.country}}","operator":"is","values":["US","CA"]},"continue_workflow_if_action_fails":false}},"ConfigureActionWarning":{"type":"object","description":"A non-fatal issue detected while applying a Configure Action request.","properties":{"field":{"type":"string","description":"The field name in the request body that this warning refers to.","example":"model"},"code":{"type":"string","description":"Machine-readable warning code. Known values: `unknown_field` (field not in the action's template), `unresolved_reference` (a `{{...}}` reference does not match any known variable on this sheet).","example":"unknown_field"},"message":{"type":"string","description":"Human-readable warning message suitable for display.","example":"Field 'model' is not recognized by this action and was ignored."}},"required":["field","code","message"]},"RenameActionBody":{"type":"object","description":"Body for Rename Action. Sets the action instance's display name.","properties":{"name":{"type":"string","description":"New display name for the action instance. Trimmed; cannot be empty after trimming.","minLength":1,"example":"Enrich primary contact"}},"required":["name"]},"MoveActionBody":{"type":"object","description":"Body for Move Action. The moved action is placed immediately after the action identified by `after`.","properties":{"after":{"type":"string","minLength":1,"description":"An existing `action_instance_id` (NOT an `action_id`). Cannot equal the action being moved.","example":"enrich_company_linkedin_profile_1"}},"required":["after"]},"ActionGraphNode":{"type":"object","description":"One node in the workflow's action graph. `data[0]` is always the synthetic input node (`action_id: \"input\"`); subsequent elements are actions in topological execution order.","properties":{"action_instance_id":{"type":"string","description":"Unique ID of this node within the sheet. The synthetic input node uses `\"input\"`; action nodes use their per-sheet instance ID.","example":"input"},"action_id":{"type":"string","description":"Action template identifier. The synthetic input node uses `\"input\"`; action nodes use their template ID.","example":"input"},"display_name":{"type":"string","description":"User-facing name of the node.","example":"Inputs"},"inputs":{"type":"object","description":"Configured input wiring for this action: map of field name → value (literal / reference / waterfall array). Unconfigured fields are absent. Absent entirely on the input node.","additionalProperties":true},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/ActionOutput"},"description":"Fields this node produces. On the input node, one entry per configured input field. Absent on actions that produce no outputs."},"continue_workflow_if_action_fails":{"type":"boolean","description":"When `true`, downstream actions still run for a row even if this action fails. Mirrors the value set via Configure Action's `continue_workflow_if_action_fails`. Absent when the action has never had it configured."},"run_if":{"type":"object","description":"Conditional gate that decides whether this action runs for a given row. Mirrors the value set via Configure Action's `run_if`. Absent when no condition is configured. `variable` is a public reference token; `operator` is the stored comparison operator; `values` is the comparison set; `continue_workflow_if_run_condition_not_met` controls whether the chain proceeds when the condition isn't met.","properties":{"variable":{"type":"string","example":"{{input.country}}"},"operator":{"type":"string","example":"is"},"values":{"type":"array","items":{},"example":["US","CA"]},"continue_workflow_if_run_condition_not_met":{"type":"boolean","description":"When `true`, downstream actions still run for a row even when this action's condition isn't met. Default `false`."}},"required":["variable","operator","values","continue_workflow_if_run_condition_not_met"]},"next":{"type":"array","items":{"type":"string"},"description":"Downstream `action_instance_id`s. `[]` is terminal. Multiple IDs represent a branch point (reserved — currently at most one ID is populated)."},"note":{"type":"string","description":"Free-text note attached to this action via Save Action Note (or Configure Action's `note` field). Absent when no note has been saved."}},"required":["action_instance_id","action_id","display_name","next"]},"GetOptionsBody":{"type":"object","description":"Body for Get Action Field Options. `context` carries any earlier cascading selections (e.g. `{ salesforce_object: \"Account\" }` for Salesforce field lookups). Resolvers without cascades accept an empty body.","properties":{"context":{"type":"object","description":"Optional context for cascading resolvers. Keys are snake-cased and match the parent field's public name. Resolvers accept multiple snake-cased spellings — calling without a required key returns 400 listing the accepted spellings.","additionalProperties":true}}},"OptionItem":{"type":"object","description":"One value the resolver returned. `value` is what to send back via Configure Action; `label` is the human-readable text. `extras` carries integration-specific metadata when present (Salesforce field metadata, HubSpot property type, etc.).","properties":{"value":{"type":"string","description":"The value to send back via Configure Action."},"label":{"type":"string","description":"Human-readable label for the option."},"extras":{"type":"object","description":"Per-integration metadata. Shape varies by integration — use it for richer rendering when needed.","additionalProperties":true}},"required":["value","label"]},"GetOptionsData":{"type":"object","description":"Response data for Get Action Field Options.","properties":{"options":{"type":"array","items":{"$ref":"#/components/schemas/OptionItem"},"description":"Available option values for the requested field."}},"required":["options"]},"SaveNoteBody":{"type":"object","description":"Body for Save Action Note. Overwrites any existing note on the action.","properties":{"note":{"type":"string","minLength":1,"description":"Free-text note to attach to the action. Trimmed; cannot be empty after trimming.","example":"Skip until Phase 2 — vendor still negotiating contract terms."}},"required":["note"]},"ActionNote":{"type":"object","description":"A note attached to an action instance.","properties":{"action_instance_id":{"type":"string","description":"Public `action_instance_id` the note is attached to.","example":"enrich_company_linkedin_profile_1"},"sheet_id":{"type":"string","description":"Sheet the note is scoped to."},"note":{"type":"string","description":"The saved note text."}},"required":["action_instance_id","sheet_id","note"]},"RowWarning":{"type":"object","description":"A non-fatal issue detected while processing a rows batch.","properties":{"field":{"type":"string","description":"The field this warning refers to (input field name, or `_row` for row-level issues).","example":"email"},"code":{"type":"string","description":"Machine-readable warning code. Examples: `duplicate_row`, `value_normalized`.","example":"duplicate_row"},"message":{"type":"string","description":"Human-readable warning message."}},"required":["field","code","message"]},"RejectedRow":{"type":"object","description":"A row the server couldn't accept. Original content is echoed back so the caller can fix and resend without position tracking.","properties":{"row":{"type":"object","additionalProperties":true,"description":"The original row content as sent — echoed back exactly."},"errors":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string","description":"The field that failed validation, or `_row` for row-level errors.","example":"email"},"code":{"type":"string","description":"Machine-readable failure code. Known values include `malformed_row`, `unknown_field`.","example":"malformed_row"},"message":{"type":"string","description":"Human-readable failure message."}},"required":["field","code","message"]}}},"required":["row","errors"]},"AddRowsBody":{"type":"object","description":"Body for Add Rows. Accepts up to 1,000 rows per call.","properties":{"rows":{"type":"array","maxItems":1000,"minItems":1,"items":{"type":"object","additionalProperties":true},"description":"Rows to add. Each row is a JSON object whose keys are input field names (as defined by Add Inputs) and whose values match the field's declared type. Max 1,000 per call."},"run_after_add":{"type":"string","enum":["none","first_10","all"],"description":"What to run after rows are written. Scoped to the rows in this call only — pre-existing rows on the sheet are never touched. `none` (default) — add rows, don't run. `first_10` — queue up to 10 of the newly-added rows for execution; recommended build-loop default. Batches with fewer than 10 rows queue every row in the batch. `all` — queue every newly-added row (⚠ full credit cost = N rows × actions in chain; avoid until output is verified on `first_10`)."}},"required":["rows"]},"AddRowsData":{"type":"object","description":"Outcome of an Add Rows batch.","properties":{"row_count":{"type":"integer","description":"Number of rows accepted and written. Equals `row_ids.length`."},"row_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Flat array of newly-assigned row UUIDs, in request order. Rejected rows are not included. Copy directly into Run Rows's `row_ids` to execute."},"rejected":{"type":"array","items":{"$ref":"#/components/schemas/RejectedRow"},"description":"Rows the server couldn't accept (e.g. malformed shape, unknown fields). Valid rows in the same batch were still written — this list contains only the failures."},"rows_queued_for_run":{"type":"integer","description":"How many newly-added rows were queued for execution. `0` when `run_after_add: \"none\"`. `min(10, row_count)` when `\"first_10\"`. Equal to `row_count` when `\"all\"`."}},"required":["row_count","row_ids","rejected","rows_queued_for_run"]},"RunRowsBody":{"type":"object","description":"Body for Run Rows. Pass either `row_ids` (explicit list) or `first_10: true` (shortcut). Both-or-neither returns 400.","properties":{"row_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Row UUIDs to queue through the action chain. Get these from Add Rows (`row_ids` response field) or List Rows. Rows on the sheet not included here are not re-run. Mutually exclusive with `first_10`.","example":["a1b2c3d4-e5f6-7890-abcd-ef1234567001","a1b2c3d4-e5f6-7890-abcd-ef1234567002","a1b2c3d4-e5f6-7890-abcd-ef1234567003"]},"first_10":{"type":"boolean","description":"Shortcut for \"run the first 10 rows on the sheet\" (by `created_at` ascending). Useful for experimentation and sampling on existing sheets. Mutually exclusive with `row_ids`. Pass `true` to enable; omit or pass `false` to use `row_ids` instead."}}},"RunRowsData":{"type":"object","description":"Outcome of a Run Rows call.","properties":{"rows_queued":{"type":"integer","description":"Number of rows queued for execution. Equals `row_ids.length` when `row_ids` was provided, or 10 (or fewer if the sheet has fewer rows) when `first_10: true` was provided."}},"required":["rows_queued"]},"RowCell":{"type":"object","description":"One cell of a row (a single action × a single row). Branch on `status` to decide which of `outputs` / `outputs_ref` / `error` to read.","properties":{"status":{"type":"string","enum":["queued","running","complete","failed","error","condition_not_met"],"description":"Lifecycle state of this cell. Progresses `queued → running → (complete | failed | error | condition_not_met)`. `failed` and `error` are terminal failure states — `failed` is a known failure path (e.g. provider 404, validation failed); `error` is an unexpected runtime error. Treat them the same when polling for completion. `condition_not_met` is also terminal — the action's `run_if` (or an upstream `filter`) evaluated false for this row, so the cell never ran; downstream cells stay `queued` unless `continue_workflow_if_run_condition_not_met: true` was set on the gating action."},"outputs":{"type":"object","additionalProperties":true,"description":"Inline outputs for a `complete` cell when the payload is small. Keys are the action's output field names. Absent on non-`complete` cells; absent on `complete` cells with `outputs_ref`."},"outputs_ref":{"type":"object","description":"URL pointer to the outputs of a `complete` cell when the payload is too large to inline (e.g. LinkedIn profile scrapes, enrichment blobs). Fetch the URL with the caller's API key to read the data.","properties":{"url":{"type":"string","format":"uri","description":"Signed URL to fetch the outputs JSON."},"size_bytes":{"type":"integer","description":"Size of the outputs JSON at `url`, in bytes."}},"required":["url","size_bytes"]},"error":{"type":"string","description":"Human-readable failure message. Present only on `status: \"failed\"` cells."}},"required":["status"]},"RowView":{"type":"object","description":"A row on a sheet with its inputs and per-cell action status/outputs.","properties":{"row_id":{"type":"string","format":"uuid","description":"UUID assigned by Add Rows."},"row_status":{"type":"string","enum":["pending","running","complete","has_failures"],"description":"Row-level execution summary — a quick check without iterating every cell. `pending`: the row has never been run (`cells` is `{}`). `running`: at least one cell is queued or running. `complete`: all cells reached `complete`. `has_failures`: terminal state — no cell is still running and at least one cell has `status: \"failed\"` or `status: \"error\"`."},"created_at":{"type":"string","format":"date-time","description":"UTC timestamp (ISO 8601) when the row was added to the sheet via Add Rows."},"inputs":{"type":"object","additionalProperties":true,"description":"Input column values as the row was created. Keys match the sheet's input field names."},"cells":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/RowCell"},"description":"One entry per action in the sheet's chain, keyed by `action_instance_id` (e.g. `enrich_company_linkedin_profile_1`). Empty `{}` for rows that have never been run."}},"required":["row_id","row_status","created_at","inputs","cells"]},"ListRowsBody":{"type":"object","description":"Body for List Rows. All fields optional.","properties":{"row_ids":{"type":"array","maxItems":200,"items":{"type":"string","format":"uuid"},"description":"Filter to specific rows. Omit to return all rows on the sheet (paginated). Max 200 IDs per call — if you have more than 200 to poll, chunk your IDs across multiple calls."},"page_no":{"type":"integer","minimum":1,"description":"Page number (1-indexed). Only meaningful when browsing (no `row_ids`) or when `row_ids` count exceeds `page_size`."},"page_size":{"type":"integer","minimum":1,"maximum":200,"description":"Rows per page. Defaults to 20. Maximum 200."}}},"ListRowsData":{"type":"object","description":"Paginated list of rows with their inputs and per-cell status.","properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/RowView"},"description":"Rows on this page, in insertion order."},"total_count":{"type":"integer","description":"Total rows being paginated. In browse mode (no `row_ids`): the sheet-wide row count. In filter mode (`row_ids` provided): the size of the `row_ids` filter — the number of IDs the caller asked about, not the sheet's total."},"page_no":{"type":"integer"},"page_size":{"type":"integer"}},"required":["rows","total_count","page_no","page_size"]},"DeleteRowsBody":{"type":"object","description":"Body for Delete Rows. Accepts up to 200 row UUIDs per call.","properties":{"row_ids":{"type":"array","maxItems":200,"minItems":1,"items":{"type":"string"},"description":"Row UUIDs to delete. From Add Rows (`row_ids` response) or List Rows. Max 200 per call."}},"required":["row_ids"]},"DeletedRowRejection":{"type":"object","description":"A row UUID the server couldn't delete, with a human-readable reason.","properties":{"row_id":{"type":"string","description":"The UUID you sent that couldn't be deleted."},"error":{"type":"string","description":"Human-readable reason (e.g. `Row not found on this sheet`)."}},"required":["row_id","error"]},"DeleteRowsData":{"type":"object","description":"Outcome of a Delete Rows batch.","properties":{"deleted_count":{"type":"integer","description":"Number of rows actually deleted. Equals `deleted_row_ids.length`."},"deleted_row_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"UUIDs that were successfully deleted, in request order (skipping any rejected)."},"rejected":{"type":"array","items":{"$ref":"#/components/schemas/DeletedRowRejection"},"description":"Row UUIDs the server couldn't delete. Rows that WERE deleted in the same batch are still gone — this list contains only the failures."}},"required":["deleted_count","deleted_row_ids","rejected"]},"DeleteAllRowsData":{"type":"object","description":"Outcome of a Delete All Rows call.","properties":{"deleted_count":{"type":"integer","description":"Number of rows deleted. `0` if the sheet was already empty."}},"required":["deleted_count"]}}},"paths":{"/api/v1/workflows/":{"post":{"operationId":"createWorkflow","summary":"Create Workflow","tags":["Workflows"],"description":"Creates a new empty workflow with a main sheet. Returns the `workflow_id` — this is also the main sheet's `sheet_id` (the two IDs are equal for main sheets).","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkflowBody"}}}},"security":[{"apiKey":[]}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/Workflow"}}},"example":{"status":201,"data":{"workflow_id":"531952c2-3d07-4be2-8c4b-733acba3187b","name":"Lead Enrichment Pipeline","created_at":"2026-04-20T10:30:00Z","updated_at":"2026-04-20T10:30:00Z","last_run_at":null}}}}},"400":{"description":"Missing required fields","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Missing required fields"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}},"description":"Maximum number of requests allowed in the current window"},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}},"description":"Number of requests remaining in the current window"},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}},"description":"Unix timestamp (seconds) when the current rate limit window resets"},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}},"description":"Number of seconds to wait before retrying"}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}},"get":{"operationId":"listWorkflows","summary":"List Workflows","tags":["Workflows"],"description":"Returns all workflows in the caller's organization, sorted by most recently updated first. Each entry carries the ID, name, and timestamps — no deeper state.","security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Workflow"}}}},"example":{"status":200,"data":[{"workflow_id":"531952c2-3d07-4be2-8c4b-733acba3187b","name":"Lead Enrichment Pipeline","created_at":"2026-04-18T09:12:00Z","updated_at":"2026-04-20T10:30:05Z","last_run_at":"2026-04-20T10:32:00Z"},{"workflow_id":"9a2fc018-2d4a-4e41-b0e7-112233445566","name":"Inbound Demo Scoring","created_at":"2026-04-10T14:05:00Z","updated_at":"2026-04-19T22:14:00Z","last_run_at":null}]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}":{"delete":{"operationId":"deleteWorkflow","summary":"Delete Workflow","tags":["Workflows"],"description":"Permanently deletes a workflow and everything in it — all sheets, inputs, actions, rows, and run history. Cannot be undone.","parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/WorkflowDeleted"}}},"example":{"status":200,"data":{"workflow_id":"531952c2-3d07-4be2-8c4b-733acba3187b","deleted":true}}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets":{"post":{"operationId":"createSheet","summary":"Create Sheet","tags":["Sheets"],"description":"Adds a new sheet to the workflow identified by `workflow_id`. Returns the full sheet object. Pass the returned `sheet_id` as the `{sheet_id}` path parameter on every sheet-scoped endpoint (Add Inputs, Add Action, Add Data, Run Sheet, etc.).\n\n**Routing data into the new sheet.** Sheets receive rows via the `send_to_sheet` action on another sheet in the same workflow. On the source sheet, add `send_to_sheet`, set `target_sheet_id` to the `sheet_id` returned here, and map source fields to the new sheet's input columns. To expand a `structured_array` (one row per array item), set `expand_from` on the `send_to_sheet` action. Full configuration: `/docs/action-detail/send_to_sheet.txt`.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSheetBody"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."}],"security":[{"apiKey":[]}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/Sheet"}}},"example":{"status":201,"data":{"sheet_id":"e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b","workflow_id":"531952c2-3d07-4be2-8c4b-733acba3187b","name":"Employees","is_main_sheet":false,"auto_run":true,"cache_enabled":true,"cache_since":null}}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request body"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}},"get":{"operationId":"listWorkflowSheets","summary":"List Sheets","tags":["Sheets"],"description":"Returns every sheet in a workflow, including the main sheet. Each entry is a summary — for the full state of a sheet, call the sheet-scoped endpoints with the returned `sheet_id` (List Inputs for input columns, Get Action Graph for the action chain, List Rows for row data and per-cell status).\n\n`data[0]` is always the main sheet — its `sheet_id` equals the `workflow_id` you passed in the path and `is_main_sheet` is `true`. Additional sheets follow in creation order.\n\n**When to use.** Before configuring `send_to_sheet` to find a `target_sheet_id`, or to enumerate `auto_run` / `cache_enabled` / `cache_since` across every sheet in a workflow.","parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Sheet"},"description":"Every sheet in the workflow. `data[0]` is the main sheet; additional sheets follow in creation order."}}},"example":{"status":200,"data":[{"sheet_id":"531952c2-3d07-4be2-8c4b-733acba3187b","workflow_id":"531952c2-3d07-4be2-8c4b-733acba3187b","name":"Lead Enrichment Pipeline","is_main_sheet":true,"auto_run":false,"cache_enabled":false,"cache_since":null},{"sheet_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","workflow_id":"531952c2-3d07-4be2-8c4b-733acba3187b","name":"Employees","is_main_sheet":false,"auto_run":true,"cache_enabled":true,"cache_since":"2026-03-17"}]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/inputs":{"get":{"operationId":"listInputs","summary":"List Inputs","tags":["Inputs"],"description":"Returns all input fields currently configured for a workflow sheet. Use this to check existing inputs before adding or updating.\n\n**Sheet ID convention:** to operate on the workflow's main sheet, pass the workflow's own ID as `sheet_id` — the main sheet's ID equals the workflow ID. For additional sheets under the workflow, pass the child sheet's ID.\n\n**Referencing inputs in downstream actions:** each input's `reference` field is a ready-to-use variable token (`{{input.<name>}}`) that can be dropped into action `inputString` configurations to pull the input's value at run time.","parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Input"},"description":"Configured input fields, in the order they are defined on the sheet."}}},"example":{"status":200,"data":[{"name":"linkedin_url","type":"url","description":"LinkedIn company profile URL","reference":"{{input.linkedin_url}}"},{"name":"email","type":"email","description":"","reference":"{{input.email}}"}]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow or sheet not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow or sheet not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}},"post":{"operationId":"addInputs","summary":"Add Inputs","tags":["Inputs"],"description":"Adds input fields to the workflow. Existing inputs are not affected. Returns all inputs (existing + new). Reference input fields in actions with `{{input.field_name}}` syntax.\n\n**Request body:** a JSON array — no wrapper object needed.\n\n**Valid `type` values:** `string`, `url`, `email`, `number`.\n\n**Sheet ID convention:** to operate on the workflow's main sheet, pass the workflow's own ID as `sheet_id` — the main sheet's ID equals the workflow ID. For additional sheets under the workflow, pass the child sheet's ID.","requestBody":{"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InputCreate"},"description":"Input fields to add to the sheet. Each item: `name`, `type`, and optional `description`."},"example":[{"name":"linkedin_url","type":"url","description":"LinkedIn company profile URL"},{"name":"email","type":"email"},{"name":"company_name","type":"string","description":"Target company name"}]}},"description":"Input fields to add to the sheet. Each item: `name`, `type`, and optional `description`."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Input"},"description":"Every input on the sheet after the add — existing inputs followed by the newly added ones, each with its `reference` token."}}},"example":{"status":200,"data":[{"name":"linkedin_url","type":"url","description":"LinkedIn company profile URL","reference":"{{input.linkedin_url}}"},{"name":"email","type":"email","description":"","reference":"{{input.email}}"},{"name":"company_name","type":"string","description":"Target company name","reference":"{{input.company_name}}"}]}}}},"400":{"description":"Invalid request — e.g. duplicate field name or unsupported type","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — e.g. duplicate field name or unsupported type"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow or sheet not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow or sheet not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/inputs/{field_name}":{"delete":{"operationId":"deleteInput","summary":"Delete Input","tags":["Inputs"],"description":"Removes a single input field from the workflow. Any action references using `{{input.<field_name>}}` for this field will break. Check your action configurations before deleting.\n\n**Sheet ID convention:** to operate on the workflow's main sheet, pass the workflow's own ID as `sheet_id` — the main sheet's ID equals the workflow ID. For additional sheets under the workflow, pass the child sheet's ID.\n\n**Response:** the array of remaining inputs on the sheet after the delete, in the order they are defined.","parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."},{"schema":{"type":"string"},"examples":{"email":{"value":"email"},"linkedin_url":{"value":"linkedin_url"}},"in":"path","name":"field_name","required":true,"description":"The `name` of the input field (e.g. `email`). Use List Inputs to see all field names."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Input"},"description":"Remaining input fields on the sheet after the delete, in the order they are defined."}}},"example":{"status":200,"data":[{"name":"email","type":"email","description":"","reference":"{{input.email}}"}]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow, sheet, or field not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow, sheet, or field not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/actions/add":{"post":{"operationId":"addActionToWorkflow","summary":"Add Action to Workflow","description":"Adds an action to the workflow. Returns the `action_instance_id`, input fields to configure, and output fields with reference strings.\n\nAppends to the end of the chain by default. Use `after` to insert after a specific action — **`after` is an `action_instance_id`, NOT an `action_id`**.\n\nPass `name` to set a custom display name for the new instance. Omit to use the action's default name.After adding, use Configure Action to wire input variables.\n\n**Sheet ID convention:** to operate on the workflow's main sheet, pass the workflow's own ID as `sheet_id` — the main sheet's ID equals the workflow ID. For additional sheets under the workflow, pass the child sheet's ID.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddActionBody"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."}],"security":[{"apiKey":[]}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/ActionInstance"}}},"example":{"status":201,"data":{"action_instance_id":"scrape_person_linkedin_profile_1","action_id":"scrape_person_linkedin_profile","display_name":"Scrape Person LinkedIn Profile","inputs":[{"name":"linkedin_url","type":"url","required":true,"description":"LinkedIn profile URL of the person"}],"outputs":[{"name":"full_name","type":"string","reference":"{{scrape_person_linkedin_profile_1.full_name}}"},{"name":"headline","type":"string","reference":"{{scrape_person_linkedin_profile_1.headline}}"},{"name":"current_company","type":"string","reference":"{{scrape_person_linkedin_profile_1.current_company}}"},{"name":"current_company_domain","type":"url","reference":"{{scrape_person_linkedin_profile_1.current_company_domain}}"},{"name":"experiences","type":"raw_array","reference":"{{scrape_person_linkedin_profile_1.experiences}}","description":"Work history as JSON. Each item: title, company, start_date, end_date, summary, url, company_domain, company_identifier"},{"name":"education","type":"raw_array","reference":"{{scrape_person_linkedin_profile_1.education}}","description":"Education history as JSON. Each item: school, degree, field, start_date, end_date"}]}}}}},"400":{"description":"Invalid request — e.g. `after` refers to an action_instance_id that does not exist on this sheet","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — e.g. `after` refers to an action_instance_id that does not exist on this sheet"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow, sheet, or action template not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow, sheet, or action template not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/actions/{action_instance_id}":{"patch":{"operationId":"configureAction","summary":"Configure Action","description":"Configures an action instance. Send only the fields you want to set — unset fields keep their current value.\n\n**Body fields:**\n- `inputs` — map of field name → value. String values can be literals, public reference tokens (`{{input.<name>}}` / `{{<action_instance_id>.<name>}}`), or a mix. String-array values are used for waterfall-provider fields (ordered provider IDs).\n- `run_if` — optional gate. The action runs for a row only when the resolved value of `variable` satisfies `operator` against `values`. Pass `null` to clear an existing condition.\n- `continue_workflow_if_action_fails` — when `true`, downstream actions still run for a row even if this action fails.\n- `note` — optional free-text note to save on the action. Same upsert as Save Action Note (`POST /:action_instance_id/notes`).\n\nFor action-specific configuration guidance (prompts, waterfall provider IDs, model selection), see the action detail file at `/docs/action-detail/{action_id}.txt`.\n\n**Cache invalidation.** Any change to this action's configuration — including whitespace-only edits — invalidates the cache for every row already processed through it. That action, and every action downstream, re-runs and re-bills on the next execution.\n\n**Sheet ID convention:** to operate on the workflow's main sheet, pass the workflow's own ID as `sheet_id` — the main sheet's ID equals the workflow ID. For additional sheets under the workflow, pass the child sheet's ID.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfigureActionBody"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."},{"schema":{"type":"string"},"examples":{"enrich_company_linkedin_profile_1":{"value":"enrich_company_linkedin_profile_1"},"llm_models_1":{"value":"llm_models_1"}},"in":"path","name":"action_instance_id","required":true,"description":"The action instance ID (e.g. `enrich_company_linkedin_profile_1`). Returned by Add Action to Workflow."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/ConfigureActionWarning"},"description":"Non-fatal issues detected while applying the request (e.g. unknown fields, unresolved references). Absent or empty when the request was applied cleanly."},"data":{"type":"object","properties":{"action_instance_id":{"type":"string","description":"Echoes the action instance that was configured.","example":"llm_models_1"}},"required":["action_instance_id"]}}},"example":{"status":200,"warnings":[{"field":"model","code":"unknown_field","message":"Field 'model' is not recognized by this action and was ignored."},{"field":"prompt","code":"unresolved_reference","message":"Reference {{input.linkdin_url}} does not match any input field. Did you mean {{input.linkedin_url}}?"}],"data":{"action_instance_id":"llm_models_1"}}}}},"400":{"description":"Invalid request body","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request body"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow, sheet, or action instance not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow, sheet, or action instance not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}},"delete":{"operationId":"deleteAction","summary":"Delete Action","description":"Removes an action from the sheet's chain by relinking its previous and next neighbours so the chain skips it.\n\n**Returns the affected inputs.** Any action whose configuration references one of the deleted action's outputs is listed in `dependent_actions[]`, with `inputs` carrying only the fields that actually referenced the deleted action. Values are translated to public reference form so the broken refs are easy to spot. Unrelated fields on the same dependent action are not included. The caller is expected to fix the listed fields via Configure Action.\n\nCannot delete the input action — pass any other `action_instance_id`.\n\n**Sheet ID convention:** to operate on the workflow's main sheet, pass the workflow's own ID as `sheet_id` — the main sheet's ID equals the workflow ID. For additional sheets under the workflow, pass the child sheet's ID.","parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."},{"schema":{"type":"string"},"examples":{"enrich_company_linkedin_profile_1":{"value":"enrich_company_linkedin_profile_1"},"llm_models_1":{"value":"llm_models_1"}},"in":"path","name":"action_instance_id","required":true,"description":"The action instance ID to delete (e.g. `enrich_company_linkedin_profile_1`). Returned by Add Action to Workflow, or found in the Get Action Graph response."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"type":"object","properties":{"deleted":{"type":"boolean","description":"Always `true` on a 200 response — the action was removed from the chain."},"action_instance_id":{"type":"string","description":"Echoes the action instance that was deleted."},"dependent_actions":{"type":"array","description":"Actions whose configuration referenced one of the deleted action's outputs. Empty when nothing depended on it. Each entry carries only the affected fields — keys are the dependent action's input field names (snake-cased), values are the stored `inputString` translated to public reference form so the broken refs are obvious. The caller is expected to re-wire these via Configure Action.","items":{"type":"object","properties":{"action_instance_id":{"type":"string","description":"The dependent action's instance ID."},"inputs":{"type":"object","description":"Map of `<snake_field_name>` → input value (string). Only fields that referenced the deleted action are included; unrelated fields on the same action are omitted.","additionalProperties":{"type":"string"}}},"required":["action_instance_id","inputs"]}}},"required":["deleted","action_instance_id","dependent_actions"]}}},"example":{"status":200,"data":{"deleted":true,"action_instance_id":"enrich_company_linkedin_profile_1","dependent_actions":[{"action_instance_id":"llm_models_1","inputs":{"prompt":"Write a cold email to {{enrich_company_linkedin_profile_1.company_name}} about {{enrich_company_linkedin_profile_1.industry}}."}}]}}}}},"400":{"description":"Invalid request — e.g. attempting to delete the input action (`id1`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — e.g. attempting to delete the input action (`id1`)."}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow, sheet, or action instance not found. Reserved IDs (`input`, `graph`) also return 404.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow, sheet, or action instance not found. Reserved IDs (`input`, `graph`) also return 404."}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}},"get":{"operationId":"getAction","summary":"Get Action","description":"Returns a single action from a workflow. The response `data` is the same per-node shape emitted by Get Action Graph — use this endpoint when you only need one action and don't want to fetch the entire graph.\n\n**Round-tripping with Configure Action:** the `inputs` map in this response matches the body Configure Action accepts. GET the current action, modify `inputs`, then PATCH the same URL (Configure Action) to save.\n\n**Notes.** If a note has been saved on this action via Save Action Note (or Configure Action's `note` field), it's surfaced as `data.note`. Absent when no note has been saved.\n\n**Validation warnings on read.** The response includes a `warnings` array whenever the action's current configuration has issues (unresolved references, downstream references, required fields missing). Agents inspecting a workflow they didn't build — or revisiting one after changes upstream — can spot broken config without having to re-PATCH. Same warning shape as Configure Action's response, so one parsing path covers both.\n\n**Reserved `action_instance_id` values:** `\"input\"` (the input pseudo-node — use List Inputs or `data[0]` of Get Action Graph) and `\"graph\"` (the graph endpoint path — use Get Action Graph). Both return 404 here.\n\n**Sheet ID convention:** to operate on the workflow's main sheet, pass the workflow's own ID as `sheet_id` — the main sheet's ID equals the workflow ID. For additional sheets under the workflow, pass the child sheet's ID.","parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."},{"schema":{"type":"string"},"examples":{"enrich_company_linkedin_profile_1":{"value":"enrich_company_linkedin_profile_1"},"llm_models_1":{"value":"llm_models_1"}},"in":"path","name":"action_instance_id","required":true,"description":"The action instance ID (e.g. `enrich_company_linkedin_profile_1`). Returned by Add Action to Workflow, or found in the Get Action Graph response. Reserved values: `\"input\"` (input pseudo-node) and `\"graph\"` (the graph endpoint path) — both return 404 here."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/ConfigureActionWarning"},"description":"Non-fatal configuration issues detected on the current action (unresolved references, missing required fields, etc.). Absent or empty when the configuration is clean."},"data":{"$ref":"#/components/schemas/ActionGraphNode"}}},"example":{"status":200,"warnings":[{"field":"linkedin_url","code":"unresolved_reference","message":"Reference {{input.linkdin_url}} does not match any input field. Did you mean {{input.linkedin_url}}?"}],"data":{"action_instance_id":"enrich_company_linkedin_profile_1","action_id":"enrich_company_linkedin_profile","display_name":"Scrape Company LinkedIn Profile","inputs":{"linkedin_url":"{{input.linkdin_url}}"},"outputs":[{"name":"company_name","type":"string","reference":"{{enrich_company_linkedin_profile_1.company_name}}"},{"name":"industry","type":"string","reference":"{{enrich_company_linkedin_profile_1.industry}}"},{"name":"employee_count","type":"number","reference":"{{enrich_company_linkedin_profile_1.employee_count}}"}],"run_if":{"variable":"{{input.country}}","operator":"is","values":["US","CA"]},"continue_workflow_if_action_fails":false,"next":["llm_models_1"]}}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow, sheet, or action instance not found. Also returned for reserved IDs `\"input\"` and `\"graph\"`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow, sheet, or action instance not found. Also returned for reserved IDs `\"input\"` and `\"graph\"`."}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/actions/{action_instance_id}/name":{"patch":{"operationId":"renameAction","summary":"Rename Action","description":"Sets the display name of an action instance. Single-field PATCH — the only body field is `name`.\n\n**Cache.** Renaming does NOT invalidate the row-level cache for this action — display name doesn't change what the action does. Only the workflow's `updated_at` timestamp is bumped.\n\n**Sheet ID convention:** to operate on the workflow's main sheet, pass the workflow's own ID as `sheet_id` — the main sheet's ID equals the workflow ID. For additional sheets under the workflow, pass the child sheet's ID.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameActionBody"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."},{"schema":{"type":"string"},"examples":{"enrich_company_linkedin_profile_1":{"value":"enrich_company_linkedin_profile_1"},"llm_models_1":{"value":"llm_models_1"}},"in":"path","name":"action_instance_id","required":true,"description":"The action instance ID (e.g. `enrich_company_linkedin_profile_1`). Returned by Add Action to Workflow."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"type":"object","properties":{"action_instance_id":{"type":"string"},"display_name":{"type":"string"}},"required":["action_instance_id","display_name"]}}},"example":{"status":200,"data":{"action_instance_id":"enrich_company_linkedin_profile_1","display_name":"Enrich primary contact"}}}}},"400":{"description":"Invalid request — e.g. `name` is empty after trimming","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — e.g. `name` is empty after trimming"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow, sheet, or action instance not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow, sheet, or action instance not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/actions/{action_instance_id}/move":{"patch":{"operationId":"moveAction","summary":"Move Action","description":"Re-orders an action within the sheet's chain. Updates chain pointers on the moved action, its old neighbours, and its new neighbours; does not touch the action's configured inputs or outputs.\n\n**Positioning.** `after` is required. Pass an existing `action_instance_id` to place the action immediately after that instance. `after` cannot equal the action being moved.\n\nCannot move the input action — it's the chain's anchor.\n\n**Reference safety.** Move does NOT rewrite the action's configured inputs. If the new position pushes the action ahead of one of its upstream references — or pushes a downstream consumer ahead of this action — those references will fail at run time. Fix broken refs afterwards via Configure Action.\n\n**Sheet ID convention:** to operate on the workflow's main sheet, pass the workflow's own ID as `sheet_id` — the main sheet's ID equals the workflow ID.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoveActionBody"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."},{"schema":{"type":"string"},"examples":{"enrich_company_linkedin_profile_1":{"value":"enrich_company_linkedin_profile_1"},"llm_models_1":{"value":"llm_models_1"}},"in":"path","name":"action_instance_id","required":true,"description":"The action instance ID to move (e.g. `enrich_company_linkedin_profile_1`). Returned by Add Action to Workflow, or found in the Get Action Graph response."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"type":"object","properties":{"moved":{"type":"boolean","description":"Always `true` on a 200 response — the action has been re-ordered."},"action_instance_id":{"type":"string","description":"Echoes the action instance that was moved."},"after":{"type":"string","description":"The `action_instance_id` the moved action now sits immediately after. `\"input\"` indicates the moved action now sits at the start of the chain (right after the synthetic input node)."}},"required":["moved","action_instance_id","after"]}}},"example":{"status":200,"data":{"moved":true,"action_instance_id":"enrich_company_linkedin_profile_1","after":"scrape_person_linkedin_profile_1"}}}}},"400":{"description":"Invalid request — e.g. attempting to move the input action (`id1`), `after` references the action being moved, or `after` references an action that does not exist on this sheet.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — e.g. attempting to move the input action (`id1`), `after` references the action being moved, or `after` references an action that does not exist on this sheet."}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow, sheet, or action instance not found. Reserved IDs (`input`, `graph`) also return 404.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow, sheet, or action instance not found. Reserved IDs (`input`, `graph`) also return 404."}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/actions/{action_instance_id}/notes":{"post":{"operationId":"saveActionNote","summary":"Save Action Note","description":"Saves a free-text note on an action instance. One note per action — calling again for the same action overwrites the existing note. Notes are surfaced back on the action via Get Action / Get Action Graph (read path lands later).\n\n**Sheet ID convention:** to operate on the workflow's main sheet, pass the workflow's own ID as `sheet_id` — the main sheet's ID equals the workflow ID. For additional sheets under the workflow, pass the child sheet's ID.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveNoteBody"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."},{"schema":{"type":"string"},"examples":{"enrich_company_linkedin_profile_1":{"value":"enrich_company_linkedin_profile_1"},"llm_models_1":{"value":"llm_models_1"}},"in":"path","name":"action_instance_id","required":true,"description":"The action instance to attach the note to (e.g. `enrich_company_linkedin_profile_1`). Returned by Add Action."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/ActionNote"}}},"example":{"status":200,"data":{"action_instance_id":"enrich_company_linkedin_profile_1","sheet_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","note":"Skip this until Phase 2 — vendor still negotiating contract terms."}}}}},"400":{"description":"Invalid request — e.g. `note` is empty after trimming","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — e.g. `note` is empty after trimming"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow, sheet, or action instance not found / reserved","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow, sheet, or action instance not found / reserved"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/actions/{action_instance_id}/options/{field_name}":{"post":{"operationId":"getOptions","summary":"Get Action Field Options","description":"Fetches the dropdown / dynamic option values for one of an action's input fields. Use it when configuring an action whose field expects a value from a connected integration (Salesforce objects, Instantly campaigns, HubSpot properties, etc.).\n\nPass the action's `action_instance_id` and the snake-cased `field_name` (both come back in Add Action's response) — the server dispatches to the matching resolver internally. There is no `reference_id` / `valuesId` for callers to track.\n\nCascading resolvers (e.g. Salesforce: pick object → pick external ID field) take their parent selection via `context`. Each cascading resolver declares its required context keys; calling without them returns a 400 with the list of missing keys so callers can fetch the parent first.\n\nReturns options as `{ value, label, extras? }`. `value` is what to send back via Configure Action; `extras` carries per-integration metadata when relevant.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetOptionsBody"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."},{"schema":{"type":"string"},"examples":{"salesforce_upsert_record_1":{"value":"salesforce_upsert_record_1"},"hubspot_create_object_1":{"value":"hubspot_create_object_1"}},"in":"path","name":"action_instance_id","required":true,"description":"The action instance ID whose field needs options. Returned by Add Action, Get Action, or Get Action Graph."},{"schema":{"type":"string"},"examples":{"salesforce_object":{"value":"salesforce_object"},"hubspot_object_properties":{"value":"hubspot_object_properties"},"select_slack_channel":{"value":"select_slack_channel"}},"in":"path","name":"field_name","required":true,"description":"Snake-cased input field name from the action's `inputs[]` (matches what Add Action returns and what Configure Action accepts)."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/GetOptionsData"}}},"example":{"status":200,"data":{"options":[{"value":"Account","label":"Account"},{"value":"Contact","label":"Contact"},{"value":"Lead","label":"Lead"}]}}}}},"400":{"description":"Invalid request — e.g. the field is not dynamic on this action, the field name doesn't exist on the template, or required `context` keys are missing.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — e.g. the field is not dynamic on this action, the field name doesn't exist on the template, or required `context` keys are missing."}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow / sheet / action instance not found, or the action_instance_id is reserved (`input`, `graph`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow / sheet / action instance not found, or the action_instance_id is reserved (`input`, `graph`)."}}}},"424":{"description":"The user is not connected to the underlying integration (e.g. no Salesforce OAuth). Surface as a 'connect first' CTA.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"The user is not connected to the underlying integration (e.g. no Salesforce OAuth). Surface as a 'connect first' CTA."}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}},"502":{"description":"Upstream integration call failed (provider down, expired token, etc.).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Upstream integration call failed (provider down, expired token, etc.)."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/actions/{action_instance_id}/outputs":{"get":{"operationId":"getActionOutputSchema","summary":"Get Action Output Schema","description":"Returns the resolved output schema for an action — every field the action produces, each with a ready-to-paste `reference` token for downstream wiring.\n\n**When to use this** instead of reading `outputs` from Get Action / Get Action Graph: dynamic-output actions (e.g. `http_api_call`, `raw_to_structured_array`) only know their output shape after a row runs. Get Action returns whatever's currently stored, which is empty / partial before the first run; this endpoint is the canonical place to refresh after a run. Static actions (Salesforce / HubSpot / etc.) work too — same payload as Get Action's `outputs`.\n\n**`structured_array` outputs** (a list of structured rows) carry their per-column schema under `columns[]`, each with its own `structured_array_reference` token shaped `{{<action_instance_id>.<list>.<column>}}`. Drop those tokens verbatim into a downstream action's Configure Action body — the runtime is the source of truth for reference syntax.\n\n**Reserved `action_instance_id` values:** `\"input\"` (use List Inputs) and `\"graph\"` (the graph endpoint path) return 404.","parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."},{"schema":{"type":"string"},"examples":{"raw_to_structured_array_1":{"value":"raw_to_structured_array_1"},"http_api_call_1":{"value":"http_api_call_1"}},"in":"path","name":"action_instance_id","required":true,"description":"The action instance whose output schema to return. Returned by Add Action, Get Action, or Get Action Graph."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"type":"object","properties":{"action_instance_id":{"type":"string","description":"Echo of the path parameter, for response-level self-description."},"outputs":{"type":"array","items":{"$ref":"#/components/schemas/ActionOutput"},"description":"Fields this action produces, each with a ready-to-paste `reference` token for downstream wiring. Empty array for actions that produce no fields (e.g. `filter`)."}},"required":["action_instance_id","outputs"]}}},"example":{"status":200,"data":{"action_instance_id":"raw_to_structured_array_1","outputs":[{"name":"list","type":"structured_array","reference":"{{raw_to_structured_array_1.list}}","description":"Structured array built from the input array.","columns":[{"name":"first_name","type":"string","structured_array_reference":"{{raw_to_structured_array_1.list.first_name}}"},{"name":"email","type":"email","structured_array_reference":"{{raw_to_structured_array_1.list.email}}"}]}]}}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow / sheet / action instance not found, or the action_instance_id is reserved (`\"input\"`, `\"graph\"`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow / sheet / action instance not found, or the action_instance_id is reserved (`\"input\"`, `\"graph\"`)."}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/actions/graph":{"get":{"operationId":"getActionGraph","summary":"Get Action Graph","description":"Returns every node in a workflow as a flat array, ordered topologically from the input node onward.\n\n**Reading the response:**\n- `data[0]` is always the input node (`action_id: \"input\"`). It represents the workflow's input columns and has no `inputs` of its own — its `outputs` list every input field with ready-to-paste `{{input.<field_name>}}` references. If no inputs have been defined yet, `outputs: []`.\n- `data[1..]` are the actions, in topological execution order from the entry action onward. If no actions have been added yet, `data` contains only the input node with `next: []`.\n- Walk the chain by following each node's `next` array. `next: []` is terminal.\n- Multiple IDs in `next` represent a split path — today only one downstream ID is populated, but the shape accommodates future branching actions without changing.\n- Resolving variable references: `{{X.field}}` → find the node where `action_instance_id === \"X\"` → look in its `outputs`. This rule works uniformly for `{{input.linkedin_url}}` and for action outputs like `{{enrich_company_linkedin_profile_1.company_name}}`.\n\n**What each action node carries:**\n- `inputs` — the variable wiring you set via Configure Action. Unconfigured fields are simply absent from the map.\n- `outputs` — the fields this action produces, each with a ready-to-paste `reference` string. Absent on actions with no outputs (e.g. `filter`).\n- `continue_workflow_if_action_fails` — present only when configured via Configure Action. `true` lets the chain skip past this action's failure for a row.\n- `run_if` — present only when a condition is configured via Configure Action. The action runs for a row only when the resolved `variable` satisfies `operator` against `values`.\n- `note` — present only when a note has been saved on the action via Save Action Note or Configure Action's `note` field.\n\nUse this endpoint to inspect the full graph of a workflow before modifying individual actions. For a single action in isolation, use Get Action.\n\n**Sheet ID convention:** to operate on the workflow's main sheet, pass the workflow's own ID as `sheet_id` — the main sheet's ID equals the workflow ID. For additional sheets under the workflow, pass the child sheet's ID.","parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ActionGraphNode"},"description":"Flat array of graph nodes in topological order. First element is always the input node."}}},"example":{"status":200,"data":[{"action_instance_id":"input","action_id":"input","display_name":"Inputs","outputs":[{"name":"linkedin_url","type":"url","reference":"{{input.linkedin_url}}"},{"name":"company_name","type":"string","reference":"{{input.company_name}}"}],"next":["enrich_company_linkedin_profile_1"]},{"action_instance_id":"enrich_company_linkedin_profile_1","action_id":"enrich_company_linkedin_profile","display_name":"Scrape Company LinkedIn Profile","inputs":{"linkedin_url":"{{input.linkedin_url}}"},"outputs":[{"name":"company_name","type":"string","reference":"{{enrich_company_linkedin_profile_1.company_name}}"},{"name":"industry","type":"string","reference":"{{enrich_company_linkedin_profile_1.industry}}"},{"name":"employee_count","type":"number","reference":"{{enrich_company_linkedin_profile_1.employee_count}}"}],"run_if":{"variable":"{{input.country}}","operator":"is","values":["US","CA"]},"continue_workflow_if_action_fails":false,"next":["llm_models_1"]},{"action_instance_id":"llm_models_1","action_id":"llm_models","display_name":"AI Generate Content","inputs":{"prompt":"Write a cold email to {{enrich_company_linkedin_profile_1.company_name}} about {{enrich_company_linkedin_profile_1.industry}}.","model":"claude_4_sonnet"},"outputs":[{"name":"generated_content","type":"string","reference":"{{llm_models_1.generated_content}}"}],"next":[]}]}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow or sheet not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow or sheet not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/rows":{"post":{"operationId":"addRows","summary":"Add Rows","tags":["Rows"],"description":"Adds rows to a sheet. Each row's keys are input field names (from Add Inputs); values are written as-is — there is no type checking at the API layer, so any type mismatches surface later when downstream actions try to consume the data. Missing fields are stored as `null`; unknown fields come back in `rejected`.\n\n**Partial success — never atomic.** Accepted rows are written and their UUIDs returned in `row_ids` — pipe directly into Run Rows's `row_ids`. Any rows the server rejects are echoed back in `rejected` with their original content and field-level error codes so you can fix and resend just those. A batch of 10,000 with 5 bad rows writes 9,995 and returns the 5 failures in `rejected`.\n\nMax 1,000 rows per call.\n\n**Sheet ID convention:** to operate on the workflow's main sheet, pass the workflow's own ID as `sheet_id` — the main sheet's ID equals the workflow ID.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddRowsBody"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."}],"security":[{"apiKey":[]}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/RowWarning"},"description":"Non-blocking issues across the batch (e.g. duplicate detection, normalized values). Shape matches Configure Action's warnings."},"data":{"$ref":"#/components/schemas/AddRowsData"}}},"example":{"status":201,"warnings":[],"data":{"row_count":11,"row_ids":["a1b2c3d4-e5f6-7890-abcd-ef1234567001","a1b2c3d4-e5f6-7890-abcd-ef1234567002","a1b2c3d4-e5f6-7890-abcd-ef1234567003","a1b2c3d4-e5f6-7890-abcd-ef1234567004","a1b2c3d4-e5f6-7890-abcd-ef1234567005","a1b2c3d4-e5f6-7890-abcd-ef1234567006","a1b2c3d4-e5f6-7890-abcd-ef1234567007","a1b2c3d4-e5f6-7890-abcd-ef1234567008","a1b2c3d4-e5f6-7890-abcd-ef1234567009","a1b2c3d4-e5f6-7890-abcd-ef1234567010","a1b2c3d4-e5f6-7890-abcd-ef1234567011"],"rejected":[],"rows_queued_for_run":10}}}}},"400":{"description":"Invalid request — e.g. more than 1,000 rows","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — e.g. more than 1,000 rows"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow or sheet not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow or sheet not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/run":{"post":{"operationId":"runRows","summary":"Run Rows","tags":["Rows"],"description":"Runs a subset of the sheet's rows through the action chain. Specify the subset in one of two ways:\n\n- `row_ids` — array of specific row UUIDs (from Add Rows or List Rows). Rows not listed are untouched.\n- `first_10: true` — shortcut for \"run the first 10 rows on the sheet\" (by `created_at` ascending). Safe experimentation default.\n\nExactly one of `row_ids` or `first_10` must be provided. Both or neither returns 400.\n\n**Asynchronous.** The endpoint returns immediately after queueing. Poll List Rows for per-cell status and outputs — each cell (one action × one row) queues, runs, and bills independently.\n\nTo run every row on the sheet, use Run All Rows instead (requires `confirm_sheet_id` for safety).\n\nPrerequisites: the sheet has at least one action configured, and the rows in `row_ids` (if provided) already exist on the sheet.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunRowsBody"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"message":{"type":"string"},"data":{"$ref":"#/components/schemas/RunRowsData"}}},"example":{"status":200,"message":"Rows queued for execution","data":{"rows_queued":3}}}}},"400":{"description":"Invalid request — e.g. neither `row_ids` nor `first_10` provided, or both provided","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — e.g. neither `row_ids` nor `first_10` provided, or both provided"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow or sheet not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow or sheet not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/run-all":{"post":{"operationId":"runAllRows","summary":"Run All Rows","tags":["Rows"],"description":"Queues every row on the sheet for execution through the action chain. No request body required — the path is the contract.\n\nSame async semantics as Run Rows — the endpoint returns immediately after queueing; poll List Rows for per-cell status and outputs.\n\n⚠️ **Credit cost.** Scales with `(rows on sheet) × (actions in chain)`. A sheet with 10,000 rows and 5 actions costs 50,000 cell executions. Do not call this without first running a sample (e.g. `run_after_add: \"first_10\"` on Add Rows, or Run Rows with `first_10: true`) and verifying outputs. See the *Run a Workflow* tag for the full build-loop guidance.\n\nSeparate from Run Rows (which takes `row_ids` or `first_10`) so an agent can't accidentally run the whole sheet by misinterpreting a filter. Cache-enabled cells with unchanged resolved inputs still hit cache and don't re-bill.","parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"message":{"type":"string"},"data":{"$ref":"#/components/schemas/RunRowsData"}}},"example":{"status":200,"message":"All rows queued for execution","data":{"rows_queued":157}}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow or sheet not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow or sheet not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/actions/{action_instance_id}/run":{"post":{"operationId":"runAction","summary":"Run Action","tags":["Rows"],"description":"Runs a specific action against a set of rows. By default the processor runs ONLY the targeted action — downstream actions in the chain are not touched. Set `run_next_action: true` to continue execution down the chain after this action completes, re-running every action that comes after it in the chain.\n\n**Body:** `{ row_ids: [...], run_next_action?: boolean }`.\n- `row_ids` — required. Pass `[]` to queue *every row on the sheet* (analogous to Run All Rows but scoped to one action). Provide explicit IDs (from Add Rows or List Rows) to target a subset.\n- `run_next_action` — `false` (default) runs only the targeted action; `true` continues into the rest of the chain after this action finishes. Useful after Configure Action when you want to refresh a single action (and every action that comes after it) in one call.\n\n**Asynchronous.** Returns immediately after queueing. Poll List Rows for per-cell status and outputs — each cell (one action × one row) queues, runs, and bills independently.\n\n**Credit cost.** Scales with `(rows queued) × (actions actually run)`. With the default `run_next_action: false` that's just the targeted action; with `run_next_action: true` it's the targeted action plus every action downstream of it. Cache-enabled cells whose resolved inputs are unchanged still hit cache and don't re-bill.\n\n**Reserved `action_instance_id` values:** `\"input\"` (the synthetic input pseudo-node) and `\"graph\"` (the graph endpoint path) return 404.\n\n**Sheet ID convention:** to operate on the workflow's main sheet, pass the workflow's own ID as `sheet_id` — the main sheet's ID equals the workflow ID.","requestBody":{"content":{"application/json":{"schema":{"type":"object","description":"Body for Run Action. `row_ids: []` queues every row on the sheet; `run_next_action` controls whether the chain continues past the targeted action.","properties":{"row_ids":{"type":"array","maxItems":1000,"items":{"type":"string","format":"uuid"},"description":"Row UUIDs to queue against the targeted action. From Add Rows (`row_ids` response) or List Rows. Pass `[]` to queue every row on the sheet.","example":["a1b2c3d4-e5f6-7890-abcd-ef1234567001","a1b2c3d4-e5f6-7890-abcd-ef1234567002"]},"run_next_action":{"type":"boolean","description":"Whether to continue execution down the chain after the targeted action finishes. `false` (default) runs ONLY the targeted action — downstream actions are not touched. `true` runs the targeted action then proceeds through the rest of the chain, re-running every action that comes after it in the chain."}},"required":["row_ids"]}}},"required":true,"description":"Body for Run Action. `row_ids: []` queues every row on the sheet; `run_next_action` controls whether the chain continues past the targeted action."},"parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."},{"schema":{"type":"string"},"examples":{"llm_models_1":{"value":"llm_models_1"},"person_work_email_waterfall_1":{"value":"person_work_email_waterfall_1"}},"in":"path","name":"action_instance_id","required":true,"description":"The action instance to run (and re-run the chain downstream of). Returned by Add Action."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"message":{"type":"string"},"data":{"type":"object","properties":{"rows_queued":{"type":"integer","description":"Number of rows queued for execution against the targeted action. `0` when the sheet has no rows (and `row_ids` was empty / omitted)."}},"required":["rows_queued"]}}},"example":{"status":200,"message":"Rows queued for execution","data":{"rows_queued":3}}}}},"400":{"description":"Invalid request — e.g. malformed `row_ids` list","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — e.g. malformed `row_ids` list"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow / sheet / action instance not found, or the action_instance_id is reserved (`\"input\"`, `\"graph\"`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow / sheet / action instance not found, or the action_instance_id is reserved (`\"input\"`, `\"graph\"`)."}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/rows/list":{"post":{"operationId":"listRows","summary":"List Rows","tags":["Rows"],"description":"Returns rows on a sheet with their inputs and per-cell action status/outputs. Use this to poll for results after Run Rows, or to inspect the current state of a sheet.\n\nPOST, not GET — passing up to 200 row UUIDs as a filter doesn't fit reliably in a query string, so the request body carries the filter and pagination.\n\nPass optional `row_ids` to filter — the typical flow after Add Rows or Run Rows when you want only the rows you just touched. Omit `row_ids` to browse all rows on the sheet (paginated).\n\nEach returned row includes the `inputs` you provided and a `cells` object keyed by `action_instance_id` (one entry per action in the chain). For rows that haven't been run yet, `cells` is an empty object `{}`.\n\nEach cell has a `status` that progresses `queued → running → (complete | failed | error)`. Complete cells carry their outputs inline as `outputs` when small, or as a URL under `outputs_ref` when the payload is large (e.g. LinkedIn profile scrapes, enrichment blobs — fetch the URL with your API key to read the data). Failed and errored cells carry an `error` message — treat both terminal-failure statuses the same when polling. `queued` and `running` cells carry only `status`.\n\nPaginated — default page size 20, max 200.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListRowsBody"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/ListRowsData"}}},"example":{"status":200,"data":{"rows":[{"row_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567001","row_status":"complete","created_at":"2026-04-20T10:30:00Z","inputs":{"linkedin_url":"https://linkedin.com/company/floqer","email":"hello@floqer.com","company_name":"Floqer"},"cells":{"enrich_company_linkedin_profile_1":{"status":"complete","outputs_ref":{"url":"https://storage.floqer.com/orgs/acme/cells/a1b2c3d4-scrape-1.json","size_bytes":204800}},"llm_models_1":{"status":"complete","outputs":{"generated_content":"Hi Floqer team — noticed you raised a Series A..."}}}},{"row_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567002","row_status":"running","created_at":"2026-04-20T10:30:02Z","inputs":{"linkedin_url":"https://linkedin.com/company/acme","email":"contact@acme.com","company_name":"Acme"},"cells":{"enrich_company_linkedin_profile_1":{"status":"running"},"llm_models_1":{"status":"queued"}}},{"row_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567003","row_status":"has_failures","created_at":"2026-04-20T10:30:05Z","inputs":{"linkedin_url":"not-a-real-url","email":"ops@globex.com","company_name":"Globex"},"cells":{"enrich_company_linkedin_profile_1":{"status":"failed","error":"Provider returned 404 for the given URL."},"llm_models_1":{"status":"failed","error":"Skipped: upstream action enrich_company_linkedin_profile_1 failed."}}}],"total_count":11,"page_no":1,"page_size":20}}}}},"400":{"description":"Invalid request — e.g. `row_ids` length > 200","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — e.g. `row_ids` length > 200"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow or sheet not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow or sheet not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/rows/delete":{"post":{"operationId":"deleteRows","summary":"Delete Rows","tags":["Rows"],"description":"Permanently deletes rows from a sheet. For each deleted row, the input values and all per-cell outputs across every action in the chain are removed. Cannot be undone.\n\n**Partial success — never atomic.** Row UUIDs that exist on the sheet are deleted and returned in `deleted_row_ids`. Any UUIDs the server can't delete (not found on this sheet, malformed) come back in `rejected` with a reason. A batch of 10 with 2 bad UUIDs deletes 8 and reports 2.\n\nMax 200 row UUIDs per call.\n\n**Sheet ID convention:** to operate on the workflow's main sheet, pass the workflow's own ID as `sheet_id` — the main sheet's ID equals the workflow ID.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteRowsBody"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/DeleteRowsData"}}},"example":{"status":200,"data":{"deleted_count":2,"deleted_row_ids":["a1b2c3d4-e5f6-7890-abcd-ef1234567001","a1b2c3d4-e5f6-7890-abcd-ef1234567002"],"rejected":[{"row_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567003","error":"Row not found on this sheet"}]}}}}},"400":{"description":"Invalid request — e.g. more than 200 row IDs","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — e.g. more than 200 row IDs"}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow or sheet not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow or sheet not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/rows/delete-all":{"post":{"operationId":"deleteAllRows","summary":"Delete All Rows","tags":["Rows"],"description":"Permanently deletes every row on the sheet, including all input values and per-cell outputs across every action in the chain. Cannot be undone. Use with extreme caution. No request body required — the path is the contract.\n\n**When to use.** Iterating on a workflow build: add 10 test rows → run → verify → delete all → add real 1,000 rows → run. Separate from Delete Rows (which targets specific row UUIDs) so an agent can't accidentally wipe a sheet by misinterpreting a filter.\n\n**Sheet ID convention:** to operate on the workflow's main sheet, pass the workflow's own ID as `sheet_id` — the main sheet's ID equals the workflow ID.","parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Sheet ID. To operate on the workflow's main sheet, pass the workflow's ID as `sheet_id` — the main sheet's ID equals the workflow ID."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/DeleteAllRowsData"}}},"example":{"status":200,"data":{"deleted_count":157}}}}},"401":{"description":"Unauthorized — missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unauthorized — missing or invalid API key"}}}},"403":{"description":"Forbidden — insufficient scope or no access to resource","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Forbidden — insufficient scope or no access to resource"}}}},"404":{"description":"Workflow or sheet not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow or sheet not found"}}}},"429":{"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets.","headers":{"X-RateLimit-Limit":{"schema":{"schema":{"type":"integer","examples":[60]}}},"X-RateLimit-Remaining":{"schema":{"schema":{"type":"integer","examples":[57]}}},"X-RateLimit-Reset":{"schema":{"schema":{"type":"integer","examples":[1710345600]}}},"Retry-After":{"schema":{"schema":{"type":"integer","examples":[30]}}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Too Many Requests — rate limit exceeded. Check `Retry-After` header for seconds until the limit resets."}}}}}}}},"servers":[{"url":"http://localhost:3009","description":"Local Development"},{"url":"https://api.floqer.com","description":"Production"}],"security":[{"apiKey":[]}],"tags":[{"name":"Workflows","description":"Full lifecycle management for workflows: create → define input columns (PUT /:id/inputs) → add actions → configure action inputs → add data rows (POST /:id/data) → run → monitor results (GET /:id/data). Workflows are the core abstraction — each one defines a data processing pipeline with an input table, a chain of actions, and output data.\n\n**Sheets:** A sheet is a child workflow linked to a parent via `parentWorkflowId`. Each sheet has its own data table, actions, and runs — manage it using any workflow endpoint with the sheet's own ID. Create sheets with `POST /workflows` (include `parentWorkflowId`), list them with `GET /workflows/{id}/sheets`, and reorder with `PUT /workflows/{id}/sheets/order`."}],"externalDocs":{"description":"LLM-friendly flattened reference (single text file, served alongside this spec)","url":"/docs/llms-full.txt"}}