Floqer Concepts Read this before using the API. It explains how Floqer thinks — the mental model behind every endpoint, field, and reference format. INDEX: 1. Workflows and Sheets 2. Inputs 3. Actions 4. Data Types 5. Variable References 6. Action Chain 7. Running a Workflow 8. Get Action Field Options (dynamic dropdowns) ================================================================================ 1. WORKFLOWS AND SHEETS ================================================================================ A workflow is a container for one or more sheets. When a workflow is created, a main sheet is created automatically. Every operation on inputs, actions, data, or runs happens at the sheet level. A sheet has: - Input columns (the data you feed in) - An action chain (steps that process each row) - Data rows (the actual records) - Runs (execution history) - Settings (auto_run, cache_enabled, cache_since) If additional sheets are needed — for example, to expand an array of employees into individual rows — create them with the Create Sheet endpoint (POST /workflows/{workflow_id}/sheets). Each sheet gets its own ID. Key points: - All sheets within a workflow are independent peers — no parent-child hierarchy. - The main sheet is auto-created with the workflow. Its sheet_id equals the workflow_id. The main sheet cannot be deleted separately. - Additional sheets can be added and removed at any time. - Sheet-scoped endpoints follow /workflows/{workflow_id}/sheets/{sheet_id}/... To operate on the main sheet, pass the workflow's ID as both {workflow_id} and {sheet_id}. ================================================================================ 2. INPUTS ================================================================================ Inputs are the columns of your sheet's data table. They define what data each row contains. You add inputs before adding data or configuring actions. Each input has: - name — the column header, also used as the key when adding data rows - type — helps match fields to action inputs (see Data Types below) - description — human-readable explanation of what this field contains When you add an input, the response includes a reference string you can copy directly into action configurations. Example: adding an input named "linkedin_url" of type "url" gives you the reference {{input.linkedin_url}} for use in action configuration. ⚠ The `name` you pass at create time is what `Add Rows` expects as the JSON key — case-sensitive and whitespace-sensitive. The reference token may look snake_cased (e.g. `{{input.company_name}}`), but if the input was created with `name: "Company Name"` (display-style with space + capital), the row JSON key must be the literal `"Company Name"`. Mismatch returns `unknown_field`. Inspect `List Inputs` and use the `name` value verbatim. Managing inputs: - Add Inputs (POST) — append new fields, existing inputs unaffected - List Inputs (GET) — see current fields with reference strings - Delete Input (DELETE) — remove a field (breaks any action references to it) Inputs cannot be renamed. To change a name, delete and re-add. This will break action references to the old name — reconfigure those actions after. ================================================================================ 3. ACTIONS ================================================================================ Actions are the steps in your pipeline. Each action processes every row of data and produces output fields that downstream actions can reference. Two identifiers: action_id The template identifier. Same across all workflows. Found in the action catalog. Example: enrich_company_linkedin_profile action_instance_id The unique instance in your workflow. Auto-generated when you add an action. Suffixed with _1, _2 if the same action is added multiple times. Example: enrich_company_linkedin_profile_1 This is what you use in variable references: {{enrich_company_linkedin_profile_1.company_name}} When you add an action to a workflow, the response includes: - The action_instance_id - All output fields with ready-to-use reference strings Finding the right action: Read the action catalog at https://floqer.com/docs/action-catalog.txt — it lists every action with what it needs (input types) and what it produces (output types). For full details on a specific action (configuration guide, best practices, model selection), read https://floqer.com/docs/action-detail/{action_id}.txt ================================================================================ 4. DATA TYPES ================================================================================ Every input and action output has a type. Types help agents match fields to action inputs — for example, an action that needs a URL can search for all upstream fields of type "url". Scalar types: string General text "Floqer Inc" url A URL "https://linkedin.com/company/floqer" email An email address "hello@floqer.com" number A numeric value 42 Code types: js_expression A single JavaScript expression evaluated by Floqer's data formatter. The expression's return value is what gets written into the output column — string, number, array, or object (objects and arrays are serialized). Must be a single expression (no top-level statements). For multi-line logic, wrap in an IIFE: (() => { /* logic */ return value; })(). Variable references inside the expression use Floqer's variable syntax; treat any value coming from a variable as untrusted (may be empty, malformed, or a stringified JSON blob — JSON.parse with a fallback). Full reference (patterns, gotchas, examples): https://floqer.com/docs/action-detail/format_data_using_js_expression.txt Nested types: raw_array Unstructured nested array — a flat list whose item shape isn't part of the schema. Pass the whole array as-is to the next action. Cannot be expanded into rows. Typically used for complex nested data like work experiences, education history, or skills lists. Example output: experiences (raw_array) Reference: {{action_instance_id.experiences}} — returns the full JSON array json Unstructured nested object — a JSON object whose key set isn't part of the schema. Same caller-side handling as `raw_array` (pass as-is or extract specific fields via `format_data_using_js_expression`), but the value is an object rather than an array. Cannot be expanded into rows. Example output: company_headquarters (json) Reference: {{action_instance_id.company_headquarters}} — returns the full JSON object structured_array Pre-formatted array where each item has defined fields. Can be referenced column-by-column from downstream actions, or expanded into individual rows on a new sheet using `push_data_to_sheet`. In List Rows responses, a structured_array output comes back as a flat array of objects keyed by snake_case column names (e.g. `[{first_name: "...", last_name: "..."}, ...]`). Example output: list_of_employees (structured_array) columns: first_name (string), last_name (string), linkedin_url (url), person_title (string), … Two ways to use a structured_array: 1. Per-column reference (3-segment): keep working on the current sheet — reference one column of the array as if it were a regular list. Format: {{..}} Example: {{get_employees_by_company_using_apollo_1.list_of_employees.first_name}} 2. Fan out to a new sheet: add a `push_data_to_sheet` action, select the structured_array field, and each item becomes a row on the target sheet with the defined fields as input columns. ================================================================================ 5. VARIABLE REFERENCES ================================================================================ Variable references connect actions together. They tell an action where to get its input data from. Format: {{step.field_name}} Input references: {{input.linkedin_url}} — value from the linkedin_url input column {{input.email}} — value from the email input column Action output references: {{enrich_company_linkedin_profile_1.company_name}} — company_name output {{person_work_email_waterfall_1.work_email}} — work_email output {{llm_models_1.generated_content}} — generated text Structured-array column references (3-segment): {{get_employees_by_company_using_apollo_1.list_of_employees.first_name}} {{hubspot_lookup_object_1.records.email}} — pull a single column out of an upstream structured_array. Works the same as a scalar reference but resolved per-row when the action below reads the value. Rules: - You can only reference upstream actions (actions that run before the current one) - Referencing a downstream action will fail - References are resolved per-row at runtime — each row gets its own values - You can mix static text and multiple references in a single field: "Research {{input.company_name}} at {{enrich_company_linkedin_profile_1.company_website}}" Where to find reference strings: - Add Inputs response — each input includes a "reference" field - Add Action response — each output includes a "reference" field - List Inputs / Get Action Config — same reference fields ================================================================================ 6. ACTION CHAIN ================================================================================ Actions execute in chain order. Each action runs after the previous one completes for a given row. Linear chain: input → action_1 → action_2 → action_3 Most workflows are linear. Each action appends to the end of the chain. Add actions with POST /workflows/{workflow_id}/sheets/{sheet_id}/actions/add — omit the "after" field to append to the end. Pass an optional `name` to set a custom display name at creation time. Renaming an action: PATCH /workflows/{workflow_id}/sheets/{sheet_id}/actions/{action_instance_id}/name Body: {"name": "..."} Single-field PATCH that updates the action's display name without triggering the row-level cache invalidation Configure Action would — useful when you only want to relabel a node. Re-ordering an action: PATCH /workflows/{workflow_id}/sheets/{sheet_id}/actions/{action_instance_id}/move Body: {"after": "..."} -- required Pass `"input"` to move to the start (right after the synthetic input node), or an existing `action_instance_id` to move immediately after that instance. Updates only the chain pointers on the moved action, its old neighbours, and its new neighbours — configured inputs / outputs are untouched. If the new position pushes the action ahead of one of its upstream references (or pushes a downstream consumer ahead of this action), those refs will fail at run time — re-wire via Configure Action. Saving a note on an action: POST /workflows/{workflow_id}/sheets/{sheet_id}/actions/{action_instance_id}/notes Body: {"note": "..."} -- required, non-empty Upsert — one note per action; calling again overwrites it. Notes are surfaced as `note` on Get Action and on each node of Get Action Graph. Configure Action also accepts an optional `note` body field that uses the same upsert path. Filtering: The `filter` action skips rows that don't meet a set of conditions. Rows that pass continue to the next action. Rows that fail stop here — downstream cells stay queued and never run for that row. input → action_1 → filter_1 → action_2 Only rows matching the filter conditions reach action_2. See https://floqer.com/docs/action-detail/filter.txt for the full body shape (groups, combinators, operator catalog). push_data_to_sheet: The `push_data_to_sheet` action takes a structured_array from the current sheet and expands each item into a row on a target sheet. This is how you handle one-to-many relationships — for example, one company with many employees. Sheet 1: company rows → finder action produces list_of_employees (structured_array) push_data_to_sheet: expands list_of_employees into individual rows Sheet 2: employee rows → each with first_name, linkedin_url, etc. as inputs ================================================================================ 7. RUNNING A WORKFLOW ================================================================================ After building (inputs + actions configured), add data and run: 1. Add rows — POST /workflows/{workflow_id}/sheets/{sheet_id}/rows Body: {"rows": [{...}], "run_after_add"?: "none" | "first_10" | "all"} Each row is a JSON object keyed by input field names — match the literal `name` value from List Inputs exactly (case-sensitive, whitespace-sensitive). It may differ from the reference token's snake_case form (e.g. `name: "Company Name"` but reference `{{input.company_name}}` — the row key is `"Company Name"`): {"rows": [{"linkedin_url": "https://linkedin.com/company/floqer", "email": "hello@floqer.com"}]} Returns {row_ids: [...]}. Use run_after_add="first_10" during build to queue the first 10 newly-added rows for execution in one call. 2. Run the sheet — POST /workflows/{workflow_id}/sheets/{sheet_id}/run Body: {"row_ids": [...]} to run specific rows, OR {"first_10": true} to run the first 10 rows on the sheet. Execution is asynchronous. To run every row, use POST /workflows/{workflow_id}/sheets/{sheet_id}/run-all (no body). ⚠️ Run-all is a full-credit-cost operation; verify on first_10 first. To re-run just one action against a set of rows (e.g. after Configure Action edits to that action), use Run Action: POST /workflows/{workflow_id}/sheets/{sheet_id}/actions/{action_instance_id}/run Body: {"row_ids": [...], "run_next_action"?: boolean} row_ids is required — pass [] to queue every row on the sheet. run_next_action: false (default) runs only the targeted action; true continues into the rest of the chain after this action. 3. Check results — POST /workflows/{workflow_id}/sheets/{sheet_id}/rows/list Body (all optional): {"row_ids"?: [...], "page_no"?: 1, "page_size"?: 20} Returns rows with per-cell action status and outputs. Poll until row_status is "complete" or "has_failures". ⚠ The response body can contain unescaped control characters inside cell-output string fields when those values originated from user-entered company descriptions, LinkedIn names, copy-pasted bios, or web-scrape output that retained raw newlines / tabs. Strict JSON parsers like `jq` will refuse to parse it ("Invalid string: control characters from U+0000 through U+001F must be escaped"). Python's `json.loads` and most browser JSON parsers handle it leniently. Workaround for pipelines that need jq: pre-process with `python -c "import json,sys; json.dump(json.load(sys.stdin), sys.stdout)"` to re-encode with proper escaping, or extract IDs / fields via `grep -oE` rather than `jq` selectors. You can also run during the build process to test a partially configured sheet on a few rows before adding more actions. Configuration options (set at Create Sheet, per sheet): - auto_run — run the sheet automatically when new data arrives - cache_enabled — skip rows with identical inputs (exact match only) - cache_since — earliest date (UTC) from which cached runs are considered fresh. Default: null (all cached runs are fresh regardless of age) ================================================================================ 8. GET ACTION FIELD OPTIONS (DYNAMIC DROPDOWNS) ================================================================================ Some action fields are dynamic dropdowns whose valid values depend on the caller's account or on other field selections — e.g. a HubSpot object's property list, a user's Slack channels, a Salesforce object's external-ID fields, a Google spreadsheet's tabs. The action-detail file calls these out explicitly when they exist. Resolve them by calling Get Action Field Options: POST /api/v1/workflows/{workflow_id}/sheets/{sheet_id}/actions/{action_instance_id}/options/{field_name} Body: { "context": { "": "" } } // optional Returns a list of `{ value, label }` pairs. Pass the chosen `value` straight into the matching field in Configure Action. Examples: - HubSpot properties keyed by object type: POST .../options/hubspot_object_properties Body: { "context": { "hubspot_object": "contacts" } } - Google Sheet tabs inside a chosen spreadsheet: POST .../options/select_sheet Body: { "context": { "spreadsheets": "" } } - User's Instantly campaigns (no context required): POST .../options/campaign_id Body: {} When configuring an action with one or more dynamic dropdowns, always resolve the options first — never guess values. The action-detail file lists the exact pre-call(s) needed for each action. ================================================================================ 9. CONDITIONAL EXECUTION (RUN_IF) ================================================================================ Any action can be gated with a `run_if` condition so it runs for a row only when the condition is satisfied. Set via Configure Action's body alongside `inputs`; pass `null` to clear; omit to leave existing condition unchanged. Shape: run_if: { variable: "{{}}", operator: "is" | "is not" | "is empty" | "is not empty" | "contains" | "does not contain" | "starts with" | "does not start with" | "ends with" | "does not end with" | "greater than" | "less than" | "greater than or equal to" | "less than or equal to" | "is between" | "is after" | "is before", values: ["..."] } Operator support depends on the variable's stored type — derived from the sheet's chain, so you don't pass it: - Universal (any type): is, is not, is empty, is not empty (`is empty` / `is not empty` ignore `values`) - String / email / url: contains, does not contain, starts with, does not start with, ends with, does not end with (case-insensitive) - Number / currency: greater than, less than, greater/less than or equal to, is between (pass `[min, max]`) - Date: is after, is before, is between (pass `[start, end]`) - Array / structured_array: contains, does not contain - json: contains, does not contain `values` is an array of literals — variable references inside `values` are not resolved. Number / date matchers coerce as needed. Common patterns: Lookup-then-create (when no upsert key exists): salesforce_lookup_record -> salesforce_create_record (run_if: {{lookup.record_id}} is empty) Per-row gating by enrichment result: person_work_email_waterfall -> salesforce_upsert_record (run_if: {{waterfall.work_email}} is not empty) Geographic filter: enrichment -> outreach_action (run_if: {{input.country}} is one of US, CA) When `run_if` is configured, Get Action / Get Action Graph surface it alongside `inputs` and `next` on the action node. When absent, the action runs unconditionally (the default). For the full schema and operator semantics, see Configure Action in the API reference (https://floqer.com/docs/reference#configureAction). ================================================================================ This file is maintained manually. Last updated: 2026-05-18. Full interactive reference: https://floqer.com/docs/reference