{"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: **200 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\nValidation errors (400) from source preview/create and other batched validators return every field failure at once:\n```json\n{\n  \"status\": 400,\n  \"error\": \"Validation Error\",\n  \"message\": \"2 validation errors\",\n  \"errors\": [{\"field\": \"filters.job_levels\", \"message\": \"unknown key for find_people\"}]\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| `sources:read` | Preview a data source, read imported source rows, and resolve dynamic field options (read-only — nothing is created) |\n| `sources:write` | Create sources, sync them to workflows, and pause or resume created sources |\n\n---\n\n## Quick Start — Building a Workflow via API\n\n### 0. Bootstrap\n`GET /api/v1/user/` — confirm caller identity and org membership.\n\n### 1. Create a Workflow\n`POST /api/v1/workflows/` with `{\"name\": \"My Pipeline\"}` — returns `workflow_id`.\n\n### 2. Define Input Columns\n`POST /api/v1/workflows/{workflow_id}/sheets/{sheet_id}/inputs` — add input fields (e.g. `linkedin_url`, `email`). For the main sheet, pass the workflow's own ID as `sheet_id`. Each input returns a `reference` token like `{{input.linkedin_url}}`.\n\n### 3. Add Actions\n`POST /api/v1/workflows/{workflow_id}/sheets/{sheet_id}/actions/add` — add actions by `action_id` (see `/docs/action-catalog.txt`). Returns `action_instance_id`, input fields to configure, and output `reference` tokens.\n\n### 4. Configure Action Inputs\n`PATCH /api/v1/workflows/{workflow_id}/sheets/{sheet_id}/actions/{action_instance_id}` — map variables using `{{input.<name>}}` or `{{<action_instance_id>.<name>}}` reference tokens.\n\n### 5. Add Data Rows\n`POST /api/v1/workflows/{workflow_id}/sheets/{sheet_id}/rows` — insert rows using the **field names** from step 2. Returns `row_ids` for each inserted row.\n\n### 6. Run the Workflow\n`POST /api/v1/workflows/{workflow_id}/sheets/{sheet_id}/run` — execute on specific rows:\n```json\n{\"row_ids\": [\"a1b2c3d4-...\", \"e5f6g7h8-...\"]}\n```\n\n### Variable Reference Cheat Sheet\n| Token | Example |\n|-------|--------|\n| Input reference | `{{input.linkedin_url}}` |\n| Action output reference | `{{scrape_person_linkedin_profile_1.full_name}}` |\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/{workflow_id}/sheets` with `{\"name\": \"Sheet Name\"}` — returns `sheet_id`.\n\nUse the returned `sheet_id` (or the workflow's own ID for the main sheet) with sheet-scoped endpoints.\n\n### Get Workflow Overview\n`GET /api/v1/workflows/{workflow_id}` — returns workflow metadata, every sheet with settings, and webhook URLs for pushing rows. Use this as the bootstrap call after List Workflows.\n\n### Key Points\n- The main sheet's `sheet_id` equals the workflow's `workflow_id`.\n- Child sheets are created with `POST /workflows/{workflow_id}/sheets`.\n- Delete a child sheet with `DELETE /workflows/{workflow_id}/sheets/{sheet_id}`.\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"}}},"UserOrgMembers":{"type":"object","description":"Members of the caller's organization. Emails are Floqer usernames.","properties":{"count":{"type":"integer","description":"Number of members in the organization."},"emails":{"type":"array","items":{"type":"string","format":"email"},"description":"Email address of each org member (Floqer username), sorted alphabetically."}},"required":["count","emails"]},"User":{"type":"object","description":"Caller bootstrap payload. Identity and org membership are always present. Use List Workflows for accessible workflow metadata.","properties":{"email":{"type":"string","format":"email","description":"Caller's email address (Floqer username)."},"first_name":{"type":["null","string"],"description":"Caller's first name from their Floqer profile. `null` if unset."},"last_name":{"type":["null","string"],"description":"Caller's last name from their Floqer profile. `null` if unset."},"role":{"type":"string","enum":["admin","member"],"description":"Organization role: `admin` (org lead) or `member`."},"org_id":{"type":"string","description":"UUID of the caller's organization."},"org_members":{"$ref":"#/components/schemas/UserOrgMembers"}},"required":["email","first_name","last_name","role","org_id","org_members"]},"UserKnowledge":{"type":"object","description":"The caller's organization knowledge file: a free-form text/markdown blob describing the org. `exists` is `false` and `content` is `null` when none has been written yet.","properties":{"content":{"type":["null","string"],"description":"The file's text content. `null` when no file has been written yet."},"exists":{"type":"boolean","description":"Whether a knowledge file currently exists for the org."},"updated_at":{"type":["null","string"],"format":"date-time","description":"ISO 8601 timestamp of the last write, or `null` when unset."}},"required":["content","exists","updated_at"]},"UserKnowledgeBody":{"type":"object","description":"Body for Set Org Knowledge. `content` replaces the entire knowledge file.","properties":{"content":{"type":"string","description":"The full text/markdown content of the knowledge file. Replaces any existing content. Send an empty string to clear it.","example":"# Floqer\n\nGTM automation platform. Our team runs enrichment + outreach workflows: enrich inbound signups, find work emails via waterfall, and sync qualified leads to HubSpot. Goal: cut manual research time and keep the CRM enriched."}},"required":["content"]},"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"]},"WorkflowOverviewSheet":{"type":"object","description":"A sheet's operational settings within the parent workflow returned by Get Workflow Overview. `sheet_id` is the UUID to pass on sheet-scoped endpoints.","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`."},"name":{"type":"string","description":"Sheet name as set at creation. Not required to be unique within a workflow."},"is_main_sheet":{"type":"boolean","description":"`true` for the workflow's main sheet — auto-created with the workflow, always present, and has `sheet_id === data.workflow_id`."},"auto_run":{"type":"boolean","description":"When `true` (default), the sheet's action chain runs automatically whenever new data rows arrive."},"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."},"cache_since":{"type":["null","string"],"format":"date","description":"Earliest date (`YYYY-MM-DD`, UTC) from which cached runs are considered fresh. `null` means all cached runs are considered fresh regardless of age."},"webhook_url":{"type":["null","string"],"format":"uri","description":"POST URL for pushing rows into this sheet via webhook. Send a JSON body whose keys match the sheet's input columns. `null` when the sheet has no webhook input configured."}},"required":["sheet_id","name","is_main_sheet","auto_run","cache_enabled","cache_since","webhook_url"]},"WorkflowOverview":{"type":"object","description":"Operational info for a workflow: metadata (same fields as List Workflows), sheet roster with settings and webhook URLs, and shared users.","properties":{"workflow_id":{"type":"string","format":"uuid","description":"UUID of the parent workflow."},"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."},"is_owner":{"type":"boolean","description":"`true` when the caller is the workflow owner. `shared_users` is populated only when this is `true`."},"shared_users":{"type":"array","items":{"type":"string","format":"email"},"description":"Emails the workflow is shared with. Populated only when `is_owner` is `true`; otherwise `[]`."},"sheets":{"type":"array","items":{"$ref":"#/components/schemas/WorkflowOverviewSheet"},"description":"Every sheet in the workflow with its settings and webhook URL. `sheets[0]` is the main sheet."}},"required":["workflow_id","name","created_at","updated_at","last_run_at","is_owner","shared_users","sheets"]},"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"]},"SheetDeleted":{"type":"object","description":"Confirmation envelope for a deleted child sheet.","properties":{"workflow_id":{"type":"string","format":"uuid","description":"UUID of the parent workflow the sheet belonged to."},"sheet_id":{"type":"string","format":"uuid","description":"UUID of the child sheet 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","sheet_id","deleted"]},"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. Pass `null` to clear an existing condition. Omit to leave the existing condition unchanged.\n\n`conditions` is an array of AND/OR condition GROUPS — the same shape as the filter action's `path_conditions`. The outer array is groups, joined by each group's `combinator` (AND/OR); each group's `conditions` are leaf conditions, joined by each leaf's `combinator`. The first group's and first leaf's `combinator` is ignored. A single condition is just one group with one leaf.\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":{"conditions":{"type":"array","description":"AND/OR condition groups. Outer array = groups (joined by each group's `combinator`); each group's `conditions` = leaf conditions (joined by each leaf's `combinator`). A single condition is one group with one leaf.","items":{"type":"object","properties":{"conditions":{"type":"array","items":{"type":"object","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`. For `is between`, pass `[low, high]`.","items":{},"examples":[["US","CA"],["100"]]},"combinator":{"type":"string","enum":["AND","OR"],"description":"Joins this leaf with the previous one in the group. Ignored on the first leaf."}},"required":["variable","operator"]}},"combinator":{"type":"string","enum":["AND","OR"],"description":"Joins this group with the previous one. Ignored on the first group."}},"required":["conditions"]}},"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."}}},"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":{"conditions":[{"conditions":[{"variable":"{{input.country}}","operator":"is","values":["US","CA"]}]}],"continue_workflow_if_run_condition_not_met":false},"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), `deprecated_format` (the request used a deprecated-but-still-accepted shape, e.g. the legacy flat `run_if` form — migrate to the documented shape).","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"]},"ReorderWaterfallBody":{"type":"object","description":"Body for Reorder Waterfall Providers. Lists every currently configured provider `apiId` exactly once, in the desired execution order.","properties":{"providers":{"type":"array","minItems":1,"items":{"type":"string","minLength":1},"description":"Ordered list of provider `apiId`s. Must match the set of providers currently configured on the field — reorder only, no adds or removes.","example":["hunter","prospeo","findymail"]}},"required":["providers"]},"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. Always returned in the nested `{ conditions: [groups] }` form (a single condition is one group with one leaf), the same shape Configure Action accepts. `continue_workflow_if_run_condition_not_met` controls whether the chain proceeds when the condition isn't met.","properties":{"conditions":{"type":"array","description":"AND/OR condition groups, identical shape to Configure Action's `run_if.conditions`. Outer array = groups (joined by each group's `combinator`); each group's `conditions` = leaf conditions (joined by each leaf's `combinator`).","items":{"type":"object","properties":{"conditions":{"type":"array","items":{"type":"object","properties":{"variable":{"type":"string"},"operator":{"type":"string"},"values":{"type":"array","items":{}},"combinator":{"type":"string","enum":["AND","OR"]}},"required":["variable","operator","values"]}},"combinator":{"type":"string","enum":["AND","OR"]}},"required":["conditions"]}},"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":["conditions","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.\n\n**Filtering** (browse mode only — `filters` / `status_filters` / `created_at` cannot combine with `row_ids`; the server returns 400 if you mix them):\n- `filters[]` — value filters on **sheet input columns**. Each entry: `{ variable, operator, values }`. `variable` is an `{{input.<column>}}` reference token.\n- `status_filters[]` — per-action cell-status filters. Each entry: `{ action_instance_id, is?, is_not? }` (exactly one of `is` / `is_not`).\n- `created_at` — single time filter on the row's creation timestamp. `{ operator, values }`.","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. Mutually exclusive with `filters` / `status_filters` / `created_at`."},"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."},"filters":{"type":"array","description":"Value filters on sheet input columns. Each entry has `variable` (an `{{input.<column>}}` reference), `operator` (one of `is equal to`, `not equal to`, `is empty`, `not empty`, `contains`, `does not contain`, `contains any of`, `not contains any of`, `greater than`, `less than`), and `values` (array of strings — single value for most operators; multiple for `contains any of` / `not contains any of`; empty for `is empty` / `not empty`).","items":{"type":"object","properties":{"variable":{"type":"string","description":"Public reference token for the input column to filter on. Must match `{{input.<column>}}`.","example":"{{input.email}}"},"operator":{"type":"string","description":"Comparison operator. See the description above for the full list.","example":"contains"},"values":{"type":"array","items":{"type":"string"},"description":"Values to compare against. Single-value operators take one item; `contains any of` / `not contains any of` take one or more; `is empty` / `not empty` ignore values.","example":[".com"]}},"required":["variable","operator"]}},"status_filters":{"type":"array","description":"Per-action cell-status filters. Each entry specifies an `action_instance_id` and exactly one of `is` (status matches one of) or `is_not` (status matches none of). Status values use the public vocabulary (`queued | running | complete | failed | error | condition_not_met`).","items":{"type":"object","properties":{"action_instance_id":{"type":"string","description":"Action instance ID (public form) whose per-row cell status to filter on.","example":"enrich_company_linkedin_profile_1"},"is":{"type":"array","items":{"type":"string"},"description":"Match rows whose cell on this action is in one of these statuses.","example":["complete"]},"is_not":{"type":"array","items":{"type":"string"},"description":"Match rows whose cell on this action is NOT in any of these statuses.","example":["failed","error"]}},"required":["action_instance_id"]}},"created_at":{"type":"object","description":"Filter rows by creation timestamp. `operator` is one of `equals to`, `greater than`, `greater than or equals to`, `less than`, `less than or equals to`, `is between`. `values` is `[ISO timestamp]` for single-value operators or `[start, end]` for `is between`.","properties":{"operator":{"type":"string","example":"greater than"},"values":{"type":"array","items":{"type":"string"},"example":["2026-05-07T22:14:00Z"]}},"required":["operator","values"]}}},"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"]},"SourcePreviewBody":{"type":"object","description":"Per-source preview body. Shape depends on `source_id` — see the provider README under `modules/sources/` (and optionally `/docs/source-detail/{source_id}.txt`). Validated at the handler level; the schema only enforces that the body is a JSON object.","additionalProperties":true},"SourcePreviewResponse":{"type":"object","description":"Preview result envelope. `data` is capped per source (at most 100 rows — e.g. find_job_postings returns up to 25, Slack up to 10). `metadata.total_results` is the upstream count. Per-row keys depend on the source.","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Preview rows. Each row's keys depend on the source's payload."},"metadata":{"type":"object","properties":{"total_results":{"type":"integer","description":"Total matching records in this preview response."},"capped":{"type":"boolean","description":"When true, more rows exist upstream but were omitted due to preview limits. Omitted when not applicable."}},"required":["total_results"]}},"required":["data","metadata"]},"SourceCreateBody":{"type":"object","description":"Per-source create body. Shape depends on `source_id` — see the provider README under `modules/sources/` (and optionally `/docs/source-detail/{source_id}.txt`). Validated at the handler level; the schema only enforces that the body is a JSON object.","additionalProperties":true},"SourceCreateResponse":{"type":"object","description":"Create result envelope. `source_instance_id` is the new source's UUID — pass it to Sync / Get Source Data / Pause-Resume.","properties":{"source_instance_id":{"type":"string","description":"UUID of the newly created source instance."},"name":{"type":"string","description":"Display name echoed from the request (trimmed)."},"created_at":{"type":"string","format":"date-time","description":"UTC timestamp (ISO 8601) of source creation."},"pixel_id":{"type":"string","description":"Website-visitor sources only: tracking pixel id. Embed `pixel_script` on the monitored domain so visits can be recorded."},"pixel_script":{"type":"string","description":"Website-visitor sources only: HTML snippet to embed on the monitored domain."},"warning":{"type":"string","description":"Non-fatal warning when the source was created but a secondary setup step failed (e.g. Google Sheets real-time sync could not be enabled)."}},"required":["source_instance_id","name","created_at"]},"SyncSourceBody":{"type":"object","description":"Body for Sync Source to Workflow. Maps workflow inputs to source fields and (by default) backfills existing rows.","properties":{"workflow_id":{"type":"string","format":"uuid","description":"UUID of the destination workflow."},"field_mapping":{"type":"object","additionalProperties":{"type":"string"},"description":"Map of workflow input reference → source field, e.g. `{ \"input.email\": \"email\", \"input.first_name\": \"firstname\" }`. Keys are `input.<field>` references from the workflow's inputs; values are source field names. Keys are CASE-SENSITIVE and must be the exact lowercase snake_case form returned by List Inputs (the input's `reference` without the `{{ }}` braces) — e.g. `input.first_name`, not `Input.First_Name` or `input.firstName`. Surrounding `{{ }}` is tolerated; case/spelling variants are not, and return 400 `unknown input`."},"push_existing":{"type":"boolean","description":"Whether to backfill the workflow with the source's current rows. Defaults to true."},"run":{"type":"string","enum":["none","first_10","all"],"description":"Controls whether backfilled rows execute the workflow: `all` (default) runs every row, `first_10` runs the first 10 and loads the rest, `none` loads rows without running."}},"required":["workflow_id","field_mapping"],"additionalProperties":false},"SyncSourceResponse":{"type":"object","description":"Confirmation that the source is connected to the workflow.","properties":{"source_instance_id":{"type":"string","description":"The connected source's instance UUID."},"workflow_id":{"type":"string","description":"The destination workflow's UUID."},"push_existing":{"type":"boolean","description":"Whether existing rows were queued for backfill."},"run":{"type":"string","description":"The run mode applied to backfilled rows."},"fields_mapped":{"type":"integer","description":"Number of workflow inputs mapped."}},"required":["source_instance_id","workflow_id"]},"GetSourceOptionsBody":{"type":"object","description":"Body for Get Source Field Options. `context` carries any earlier cascading selections (e.g. `{ object_type: \"contacts\" }` for HubSpot property 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 on the source's payload. Resolvers accept multiple snake-cased spellings — calling without a required key returns 400 listing the accepted spellings.","additionalProperties":true}}},"SourceOptionItem":{"type":"object","description":"One value the resolver returned. `value` is what to send back in the source's create / preview payload; `label` is the human-readable text. `extras` carries integration-specific metadata when present (HubSpot property type, list color, etc.).","properties":{"value":{"type":"string","description":"The value to send back via the source's create / preview payload."},"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"]},"GetSourceOptionsData":{"type":"object","description":"Response data for Get Source Field Options.","properties":{"options":{"type":"array","items":{"$ref":"#/components/schemas/SourceOptionItem"},"description":"Available option values for the requested field."}},"required":["options"]},"SourceListItem":{"type":"object","description":"A source the caller has created. Only sources whose type this API supports are listed.","properties":{"source_instance_id":{"type":"string","description":"Source instance UUID — pass to `POST /api/v1/sources/{source_instance_id}/sync` or `GET /api/v1/sources/{source_instance_id}/data`."},"name":{"type":"string","description":"Display name given at creation."},"source_id":{"type":"string","description":"Public source-type slug (e.g. `import_from_hubspot`, `find_companies`) — the identifier used by preview / create / options."},"status":{"type":"string","description":"Source status: `active` (live, re-importing), `completed` (one-time import, won't refresh), `paused`, `paused_out_of_credits`, `expired`, or `deleted`."},"lead_count":{"type":"integer","nullable":true,"description":"Rows imported so far."},"created_at":{"type":"string","format":"date-time","description":"UTC creation timestamp."},"expiration_date":{"type":"string","nullable":true,"description":"When an active source stops firing."}},"required":["source_instance_id","name","source_id"]},"GetSourceDataResponse":{"type":"object","description":"Paginated rows imported into the source. `total_count` is the full row count in storage; `rows` is the current page.","properties":{"rows":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Imported rows for this page. Field names depend on the source type."},"total_count":{"type":"integer","description":"Total number of rows stored for this source."},"page_no":{"type":"integer","description":"1-indexed page number returned."},"page_size":{"type":"integer","description":"Page size used for this response."}},"required":["rows","total_count","page_no","page_size"]},"PatchSourceStatusBody":{"type":"object","description":"Lifecycle control for the source.","properties":{"status":{"type":"string","enum":["active","paused"],"description":"`paused` stops recurring runs; `active` resumes them."}},"required":["status"],"additionalProperties":false},"PatchSourceStatusResponse":{"type":"object","description":"Confirmation of the source's new public lifecycle status.","properties":{"source_instance_id":{"type":"string","description":"The source instance UUID."},"status":{"type":"string","enum":["active","paused"],"description":"The source's new public status (`active` or `paused`)."}},"required":["source_instance_id","status"]}}},"paths":{"/api/v1/user/":{"get":{"operationId":"getUser","summary":"Get User","tags":["User"],"description":"Returns the authenticated caller's bootstrap payload: identity and org membership. Use List Workflows for accessible workflow metadata.","security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/User"}}}}}},"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."}}}}}}},"/api/v1/user/knowledge":{"get":{"operationId":"getUserKnowledge","summary":"Get Org Knowledge","tags":["User"],"description":"Returns the caller's organization knowledge file — a single free-form text/markdown blob describing the org (who they are, what workflows they run, their goals). When no file has been written yet, returns `{ content: null, exists: false, updated_at: null }` rather than a 404. Write it with `PUT /api/v1/user/knowledge`.","security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/UserKnowledge"}}}}}},"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."}}}}}},"put":{"operationId":"putUserKnowledge","summary":"Set Org Knowledge","tags":["User"],"description":"Creates or replaces the caller's organization knowledge file — a single free-form text/markdown blob describing the org (who they are, what workflows they run, their goals). Idempotent full replacement: `content` becomes the entire file. Read it back with `GET /api/v1/user/knowledge`.\n\nMax size: 256 KB (UTF-8). To clear the file, send an empty string.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserKnowledgeBody"}}}},"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/UserKnowledge"}}}}}},"400":{"description":"Invalid request — `content` missing, not a string, or larger than 256 KB.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — `content` missing, not a string, or larger than 256 KB."}}}},"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/":{"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]}}},"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":"listWorkflows","summary":"List Workflows","tags":["Workflows"],"description":"Returns all workflows the caller can access, sorted by most recently updated first. Each entry carries the ID, name, and timestamps — no deeper state. Use Get Workflow Overview for sheet roster, settings, webhook URLs, and sharing on a single workflow.","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"}}}}}}},"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}":{"get":{"operationId":"getWorkflowOverview","summary":"Get Workflow Overview","tags":["Workflows"],"description":"Returns operational info for a single workflow — the bootstrap call after List Workflows.\n\nIncludes workflow metadata (`name`, timestamps), a per-sheet roster with settings (`auto_run`, cache), webhook URLs for pushing rows into each sheet, and sharing metadata.\n\n`data.sheets[0]` is always the main sheet; child sheets follow `sheets_config.sheetsOrder` when set, otherwise creation order.\n\n`is_owner` is `true` when the caller owns the workflow; `shared_users` is populated only for owners (other callers get `[]`).","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/WorkflowOverview"}}},"example":{"status":200,"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,"is_owner":true,"shared_users":["colleague@example.com"],"sheets":[{"sheet_id":"531952c2-3d07-4be2-8c4b-733acba3187b","name":"Lead Enrichment Pipeline","is_main_sheet":true,"auto_run":false,"cache_enabled":false,"cache_since":null,"webhook_url":"https://workers.floqer.com/v2/trigger/webhook?id=eyJ..."}]}}}}},"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."}}}}}},"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.\n\nOnly the workflow owner can delete a workflow. Shared users and org members with workflow access cannot delete workflows they do not own.","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."}}}}}}},"/api/v1/workflows/{workflow_id}/sheets/{sheet_id}":{"delete":{"operationId":"deleteSheet","summary":"Delete Sheet","tags":["Sheets"],"description":"Permanently deletes a child sheet from the workflow. Archives the sheet and its action chain; row data and run history on the sheet are no longer reachable. Cannot be undone.\n\nOnly the sheet owner can delete a child sheet. Shared users and org members with workflow access cannot delete sheets they do not own.\n\nThe workflow's main sheet cannot be deleted (`sheet_id` must not equal `workflow_id`). To remove the entire workflow including its main sheet, use Delete Workflow.\n\n**Sheet ID convention:** pass the child sheet's UUID as `sheet_id` — not the parent workflow ID.","parameters":[{"schema":{"type":"string"},"in":"path","name":"workflow_id","required":true,"description":"Parent workflow ID."},{"schema":{"type":"string"},"in":"path","name":"sheet_id","required":true,"description":"Child sheet ID to delete. Must not equal `workflow_id` (the main sheet)."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/SheetDeleted"}}},"example":{"status":200,"data":{"workflow_id":"531952c2-3d07-4be2-8c4b-733acba3187b","sheet_id":"e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b","deleted":true}}}}},"400":{"description":"Invalid request — e.g. attempting to delete the main sheet (`sheet_id === workflow_id`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — e.g. attempting to delete the main sheet (`sheet_id === workflow_id`)."}}}},"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":{"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","tags":["Actions"],"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","tags":["Actions"],"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. AND/OR condition groups: `{conditions: [{conditions: [{variable, operator, values?, combinator?}], combinator?}]}` — the same shape as the filter action's `path_conditions` (a single condition is one group with one leaf). 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","tags":["Actions"],"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","tags":["Actions"],"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":{"conditions":[{"conditions":[{"variable":"{{input.country}}","operator":"is","values":["US","CA"]}]}],"continue_workflow_if_run_condition_not_met":false},"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","tags":["Actions"],"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","tags":["Actions"],"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}/waterfall/{field_name}":{"patch":{"operationId":"reorderWaterfall","summary":"Reorder Waterfall Providers","description":"Re-orders the provider list on a waterfall (`stepDownSearch`) input field. Pass the snake-cased `field_name` from Add Action's `inputs[]` and a `providers` array listing every currently configured provider `apiId` exactly once, in the desired execution order.\n\nThis endpoint only reorders — it does not add or remove providers. To change which providers are enabled, use Configure Action.\n\n**Cache.** Same as Configure Action — reordering invalidates the row-level cache for this action and every downstream 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/ReorderWaterfallBody"}}}},"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"},"example":"person_work_email_waterfall_1","in":"path","name":"action_instance_id","required":true,"description":"The action instance whose waterfall field to reorder (e.g. `person_work_email_waterfall_1`). Returned by Add Action."},{"schema":{"type":"string"},"example":"email_providers","in":"path","name":"field_name","required":true,"description":"Snake-cased waterfall field name from the action's `inputs[]` (e.g. `email_providers`). Must be a `stepDownSearch` field on the action template."}],"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"},"field_name":{"type":"string"},"providers":{"type":"array","items":{"type":"string"},"description":"The provider `apiId`s in their new execution order."}},"required":["action_instance_id","field_name","providers"]}}},"example":{"status":200,"data":{"action_instance_id":"person_work_email_waterfall_1","field_name":"email_providers","providers":["hunter","prospeo","findymail"]}}}}},"400":{"description":"Invalid request — e.g. field is not a waterfall field, providers list is empty, contains duplicates, or does not match the currently configured provider set.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — e.g. field is not a waterfall field, providers list is empty, contains duplicates, or does not match the currently configured provider set."}}}},"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, or field not found.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Workflow, sheet, action instance, 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/{action_instance_id}/notes":{"post":{"operationId":"saveActionNote","summary":"Save Action Note","tags":["Actions"],"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","tags":["Actions"],"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","tags":["Actions"],"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","tags":["Actions"],"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. Always returned in the nested `{conditions: [groups]}` form (a single condition is one group with one leaf), the same shape Configure Action accepts.\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":{"conditions":[{"conditions":[{"variable":"{{input.country}}","operator":"is","values":["US","CA"]}]}],"continue_workflow_if_run_condition_not_met":false},"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."}}}}}}},"/api/v1/sources/{source_id}/preview":{"post":{"operationId":"previewSource","summary":"Preview Source","tags":["Sources"],"description":"Preview the records that would be ingested for the given source payload, without persisting anything. Use this to validate filters / list selections before committing to Create.\n\nPer-source body shapes and field semantics live in `/docs/source-detail/{source_id}.txt`. The route schema only enforces that the body is a JSON object; per-source validation runs inside the handler and surfaces as 400 with a specific message and, when multiple fields fail, an `errors[]` array of `{field, message}` entries.\n\nResults are typically capped at 100 rows (source-specific — e.g. Slack returns up to 10). `metadata.total_results` is the count returned in this preview. When `metadata.capped` is true, more rows exist upstream but were omitted due to preview limits.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourcePreviewBody"}}}},"parameters":[{"schema":{"type":"string"},"examples":{"extract_from_website":{"value":"extract_from_website"},"find_companies":{"value":"find_companies"},"find_companies_by_buying_intent":{"value":"find_companies_by_buying_intent"},"find_companies_by_tech_stack":{"value":"find_companies_by_tech_stack"},"find_companies_from_sales_navigator":{"value":"find_companies_from_sales_navigator"},"find_companies_hiring":{"value":"find_companies_hiring"},"find_job_postings":{"value":"find_job_postings"},"find_linkedin_post_reactors":{"value":"find_linkedin_post_reactors"},"find_people":{"value":"find_people"},"find_people_from_sales_navigator":{"value":"find_people_from_sales_navigator"},"import_from_fireflies":{"value":"import_from_fireflies"},"import_from_google_sheets":{"value":"import_from_google_sheets"},"import_from_hubspot":{"value":"import_from_hubspot"},"import_from_pipedrive":{"value":"import_from_pipedrive"},"import_from_salesforce":{"value":"import_from_salesforce"},"import_from_slack":{"value":"import_from_slack"},"import_from_snowflake":{"value":"import_from_snowflake"},"import_from_stripe":{"value":"import_from_stripe"},"import_from_typeform":{"value":"import_from_typeform"},"search_local_businesses":{"value":"search_local_businesses"},"search_x_tweets":{"value":"search_x_tweets"},"track_job_postings":{"value":"track_job_postings"},"track_linkedin_posts":{"value":"track_linkedin_posts"},"track_personal_website_visitors":{"value":"track_personal_website_visitors"},"track_website_visitors":{"value":"track_website_visitors"},"track_x_posts":{"value":"track_x_posts"}},"in":"path","name":"source_id","required":true,"description":"Source identifier. Currently supported: `extract_from_website`, `find_companies`, `find_companies_by_buying_intent`, `find_companies_by_tech_stack`, `find_companies_from_sales_navigator`, `find_companies_hiring`, `find_job_postings`, `find_linkedin_post_reactors`, `find_people`, `find_people_from_sales_navigator`, `import_from_fireflies`, `import_from_google_sheets`, `import_from_hubspot`, `import_from_pipedrive`, `import_from_salesforce`, `import_from_slack`, `import_from_snowflake`, `import_from_stripe`, `import_from_typeform`, `search_local_businesses`, `search_x_tweets`, `track_job_postings`, `track_linkedin_posts`, `track_personal_website_visitors`, `track_website_visitors`, `track_x_posts`."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/SourcePreviewResponse"}}}}}},"400":{"description":"Per-source validation failed (missing or invalid payload fields).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"errors":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["error","message"],"description":"Per-source validation failed (missing or invalid payload 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"}}}},"404":{"description":"Unknown `source_id` — see the param description for supported slugs.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unknown `source_id` — see the param description for supported slugs."}}}},"424":{"description":"The user is not connected to the integration this source depends on.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"The user is not connected to the integration this source depends on."}}}},"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/sources/{source_id}":{"post":{"operationId":"createSource","summary":"Create Source","tags":["Sources"],"description":"Create a new source. Behavior is source-specific: typically the source imports matching records immediately (when `pull_existing` or equivalent is set) and — for an ongoing source — keeps re-importing on a schedule.\n\nPer-source body shapes and field semantics live in `/docs/source-detail/{source_id}.txt`. The route schema only enforces that the body is a JSON object; per-source validation runs inside the handler and surfaces as 400 with a specific message and, when multiple fields fail, an `errors[]` array of `{field, message}` entries.\n\nReturns `source_instance_id` (the new source's UUID). The initial import runs asynchronously: the call returns as soon as the source is created and the import is queued, not when records have finished arriving.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceCreateBody"}}}},"parameters":[{"schema":{"type":"string"},"examples":{"extract_from_website":{"value":"extract_from_website"},"find_companies":{"value":"find_companies"},"find_companies_by_buying_intent":{"value":"find_companies_by_buying_intent"},"find_companies_by_tech_stack":{"value":"find_companies_by_tech_stack"},"find_companies_from_sales_navigator":{"value":"find_companies_from_sales_navigator"},"find_companies_hiring":{"value":"find_companies_hiring"},"find_job_postings":{"value":"find_job_postings"},"find_linkedin_post_reactors":{"value":"find_linkedin_post_reactors"},"find_people":{"value":"find_people"},"find_people_from_sales_navigator":{"value":"find_people_from_sales_navigator"},"import_from_fireflies":{"value":"import_from_fireflies"},"import_from_google_sheets":{"value":"import_from_google_sheets"},"import_from_hubspot":{"value":"import_from_hubspot"},"import_from_pipedrive":{"value":"import_from_pipedrive"},"import_from_salesforce":{"value":"import_from_salesforce"},"import_from_slack":{"value":"import_from_slack"},"import_from_snowflake":{"value":"import_from_snowflake"},"import_from_stripe":{"value":"import_from_stripe"},"import_from_typeform":{"value":"import_from_typeform"},"search_local_businesses":{"value":"search_local_businesses"},"search_x_tweets":{"value":"search_x_tweets"},"track_job_postings":{"value":"track_job_postings"},"track_linkedin_posts":{"value":"track_linkedin_posts"},"track_personal_website_visitors":{"value":"track_personal_website_visitors"},"track_website_visitors":{"value":"track_website_visitors"},"track_x_posts":{"value":"track_x_posts"}},"in":"path","name":"source_id","required":true,"description":"Source identifier. Currently supported: `extract_from_website`, `find_companies`, `find_companies_by_buying_intent`, `find_companies_by_tech_stack`, `find_companies_from_sales_navigator`, `find_companies_hiring`, `find_job_postings`, `find_linkedin_post_reactors`, `find_people`, `find_people_from_sales_navigator`, `import_from_fireflies`, `import_from_google_sheets`, `import_from_hubspot`, `import_from_pipedrive`, `import_from_salesforce`, `import_from_slack`, `import_from_snowflake`, `import_from_stripe`, `import_from_typeform`, `search_local_businesses`, `search_x_tweets`, `track_job_postings`, `track_linkedin_posts`, `track_personal_website_visitors`, `track_website_visitors`, `track_x_posts`."}],"security":[{"apiKey":[]}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/SourceCreateResponse"}}}}}},"400":{"description":"Per-source validation failed (missing or invalid payload fields).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"errors":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["error","message"],"description":"Per-source validation failed (missing or invalid payload 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"}}}},"402":{"description":"Credit consumption failed — the source row was archived and the initial pull did not start. Surface the message to the user; they likely need to top up.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Credit consumption failed — the source row was archived and the initial pull did not start. Surface the message to the user; they likely need to top up."}}}},"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":"Unknown `source_id` — see the param description for supported slugs.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Unknown `source_id` — see the param description for supported slugs."}}}},"424":{"description":"The user is not connected to the integration this source depends on.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"The user is not connected to the integration this source depends on."}}}},"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/sources/{source_instance_id}/sync":{"post":{"operationId":"syncSource","summary":"Sync Source to Workflow","tags":["Sources"],"description":"Connect a created source to a workflow and (by default) backfill the workflow with the source's existing rows. `source_instance_id` is the UUID returned by Create.\n\n`field_mapping` keys are public workflow input references (`input.<field>`); each value is the source field NAME to pull — the row's top-level key exactly as it appears in the Preview response (e.g. `Name`, `email`, `business_id`), NEVER a nested path like `Name.value`. The import resolves each field's value automatically, including for sources whose preview cells render as `{label, value}`. Each key is resolved to the matching workflow input. Keys are CASE-SENSITIVE — use the exact lowercase snake_case `reference` from List Inputs (minus the `{{ }}`); variants like `Input.Email` or `input.firstName` return 400. Surrounding `{{ }}` is tolerated.\n\n`run` controls whether backfilled rows execute the workflow: `all` (default) runs every row, `first_10` runs the first 10 and just loads the rest, `none` loads rows without running. `push_existing` (default true) toggles the backfill entirely.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncSourceBody"}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"source_instance_id","required":true,"description":"Source instance UUID (the `source_instance_id` returned by Create)."}],"security":[{"apiKey":[]}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/SyncSourceResponse"}}}}}},"400":{"description":"Invalid body, or a field_mapping key doesn't match a workflow input.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid body, or a field_mapping key doesn't match a workflow input."}}}},"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":"Source not found (or not owned by the caller's org).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Source not found (or not owned by the caller's org)."}}}},"409":{"description":"The source is already connected to this workflow. Update the existing connection instead of syncing again.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"The source is already connected to this workflow. Update the existing connection instead of syncing again."}}}},"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/sources/{source_instance_id}/data":{"get":{"operationId":"getSourceData","summary":"Get Source Data","tags":["Sources"],"description":"Returns paginated rows that have been imported into a created source. `source_instance_id` is the UUID returned by Create.\n\nUse this to inspect or poll a source after Create while its backfill runs, or to page through the full dataset before syncing to a workflow.\n\nQuery params `page_no` (1-indexed, default 1) and `page_size` (default 20, max 200) control pagination. `total_count` is the total number of rows stored for the source.","parameters":[{"schema":{"type":"string"},"in":"query","name":"page_no","required":false,"description":"1-indexed page number. Defaults to 1."},{"schema":{"type":"string"},"in":"query","name":"page_size","required":false,"description":"Rows per page. Defaults to 20; capped at 200."},{"schema":{"type":"string"},"in":"path","name":"source_instance_id","required":true,"description":"Source instance UUID (the `source_instance_id` returned by Create)."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/GetSourceDataResponse"}}}}}},"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":"Source not found (or not owned by the caller's org).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Source not found (or not owned by the caller's org)."}}}},"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/sources/{source_instance_id}/status":{"patch":{"operationId":"patchSourceStatus","summary":"Pause or Resume Source","tags":["Sources"],"description":"Pause or resume a created source. `source_instance_id` is the UUID returned by Create.\n\nSet `status` to `paused` to stop an ongoing source's recurring runs, or `active` to resume it. Pausing stops the schedule (when present), disables provider webhooks where applicable (e.g. Stripe, TheirStack), and prevents further imports; resuming restarts them. Static / one-time sources have nothing recurring to pause, but the status still updates.\n\nReturns the source's new public status.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchSourceStatusBody"}}}},"parameters":[{"schema":{"type":"string","format":"uuid","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"},"in":"path","name":"source_instance_id","required":true,"description":"Source instance UUID (the `source_instance_id` returned by Create)."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/PatchSourceStatusResponse"}}}}}},"400":{"description":"Invalid `source_instance_id` or `status`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid `source_instance_id` or `status`."}}}},"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":"Source not found (or not owned by the caller's org).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Source not found (or not owned by the caller's org)."}}}},"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/sources/{source_id}/options/{field_name}":{"post":{"operationId":"getSourceOptions","summary":"Get Source Field Options","tags":["Sources"],"description":"Fetches the dropdown / dynamic option values for one of a source's payload fields. Use it when building a source's preview / create payload whose field expects a value from a connected integration (HubSpot lists, HubSpot properties per object type, etc.).\n\nPass the `source_id` (same identifier used by the source's preview / create endpoints, e.g. `import_from_hubspot`) and the `field_name`. Behaves like the workflow Get Action Field Options endpoint, so resolver behavior is consistent across the two surfaces.\n\nCascading resolvers (e.g. HubSpot: pick `object_type` → list `hubspot_properties`) take their parent selection via `context`. Calling a cascading resolver without the required key returns a 400 with the accepted key spellings.\n\nReturns options as `{ value, label, extras? }`. `value` is what to send back in the source's payload; `extras` carries per-integration metadata when relevant.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSourceOptionsBody"}}}},"parameters":[{"schema":{"type":"string"},"example":"import_from_hubspot","in":"path","name":"source_id","required":true,"description":"Source identifier — same as on the source's preview / create endpoint."},{"schema":{"type":"string"},"examples":{"hubspot_lists":{"value":"hubspot_lists"},"hubspot_properties":{"value":"hubspot_properties"}},"in":"path","name":"field_name","required":true,"description":"Snake-cased payload field name to fetch options for. Matches the keys on the source's preview / create body."}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"$ref":"#/components/schemas/GetSourceOptionsData"}}},"example":{"status":200,"data":{"options":[{"value":"123","label":"All Customers"},{"value":"456","label":"Trial Signups - Last 30d"}]}}}}},"400":{"description":"Invalid request — e.g. the field has no resolver for that source, or a cascading resolver was called without its required `context` keys.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"description":"Invalid request — e.g. the field has no resolver for that source, or a cascading resolver was called without its required `context` keys."}}}},"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"}}}},"424":{"description":"The user is not connected to the underlying integration (e.g. no HubSpot 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 HubSpot 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/sources/":{"get":{"operationId":"listSources","summary":"List Sources","tags":["Sources"],"description":"List the sources you've created, newest first. Only sources whose type is supported by this API are returned (data feeds not exposed by this API are omitted).\n\nEach entry's `source_instance_id` is the source INSTANCE UUID — pass it to `POST /api/v1/sources/{source_instance_id}/sync` to connect it to a workflow. `source_id` is the source-type slug used by the preview / create / options endpoints.","security":[{"apiKey":[]}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"integer"},"data":{"type":"array","items":{"$ref":"#/components/schemas/SourceListItem"},"description":"The caller's sources, newest first."}}}}}},"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."}}}}}}}},"servers":[{"url":"http://localhost:3009","description":"Local Development"},{"url":"https://api.floqer.com","description":"Production"}],"security":[{"apiKey":[]}],"tags":[{"name":"User","description":"Caller identity and org context — the bootstrap call for an agent. `GET /api/v1/user` returns who the API key belongs to (email, name, org role) and the organization's member roster. Call this first to confirm identity before building or running workflows."},{"name":"Workflows","description":"Full lifecycle management for workflows: create → define input columns → add actions → configure action inputs → add data rows → run → monitor results. 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:** Each workflow has a main sheet (where `sheet_id` equals `workflow_id`) plus optional child sheets. Create child sheets with `POST /workflows/{workflow_id}/sheets`, inspect the full roster with `GET /workflows/{workflow_id}` (Get Workflow Overview), and delete child sheets with `DELETE /workflows/{workflow_id}/sheets/{sheet_id}`."},{"name":"Actions","description":"Configure the action chain on a workflow sheet: add actions, wire inputs with reference tokens, fetch dynamic field options, reorder, rename, and inspect outputs. All action endpoints live under `/api/v1/workflows/{workflow_id}/sheets/{sheet_id}/actions/`."},{"name":"Sources","description":"Data sources pull external data (e.g. from HubSpot, Slack) into Floqer, where it becomes available to your workflows. Each source is addressed by a `source_id` slug under `/api/v1/sources/{source_id}`.\n\nOperations per source type: **Preview**, **Create**, and **Get Source Field Options**. On created source instances: **Get Source Data**, **Sync**, and **Pause or Resume** (`PATCH .../status`). Per-source body shapes live in `/docs/source-detail/{source_id}.txt`."}],"externalDocs":{"description":"LLM-friendly flattened reference (single text file, served alongside this spec)","url":"/docs/llms-full.txt"}}