<!-- TIER:1 -- Building Flows A flow moves data from one or more source systems to one or more destination systems. It runs on a schedule, in response to events (webhooks, listeners), or when triggered by another flow. Flows are the primary way integrations get work done in Celigo. A flow has page generators (exports that fetch data) and page processors (imports and lookups that process each record). Processors run sequentially in a flat list, or conditionally through routers that branch records to different paths. These processing pipeline mechanics -- routers, branches, page processors, res…

\n description: |\n Unique identifier for this router within the flow.\n\n Used to reference this router from branches (`branches[].nextRouterId`)\n when chaining routers. Must be unique within the flow's router collection.\n Use at most 36 letters, digits, underscores and hyphens: a longer id, or one\n with a space or a period, fails the save with 422 (\"Router id is missing or\n contains more than 36 characters or invalid characters. Please correct the\n router id.\").\n examples: [\"router_001\", \"customer_type_router\"]\n name:\n type: string\n description: |\n Human-readable name for the router.\n\n Displayed in the UI and logs to help identify the router's purpose.\n examples: [\"Customer Type Router\", \"Order Status Router\"]\n routeRecordsTo:\n type: string\n description: |-\n How many branches a record may continue down. Set together with `routeRecordsUsing` on a\n branching router; leave both unset on a one-branch pass-through router.\n enum: [\"first_matching_branch\", \"all_matching_branches\"]\n x-enumDescriptions:\n first_matching_branch: Routes each record to only the first branch whose criteria match.\n all_matching_branches: Routes each record to every branch whose criteria match, creating copies.\n examples: [\"first_matching_branch\"]\n x-celigo-ai-guidance:\n - Set this and `routeRecordsUsing` together on every router that decides between branches; setting only one of the two is rejected with 422 `invalid_flow_router_config` (\"Either both the routeRecordsTo and routeRecordsUsing fields or none of them should be defined on the router.\").\n - Leave both unset on a one-branch pass-through router (the steps before the first branching router, or a merge point). Setting them there still runs, but Flow Builder shows a router node with one branch and no rules. A router with neither field and more than one branch is rejected with 422 `invalid_flow_router_branch`.\n routeRecordsUsing:\n type: string\n description: |-\n How a branching router decides which branch a record matches. Set together with\n `routeRecordsTo`; leave both unset on a one-branch pass-through router.\n enum: [\"input_filters\", \"script\"]\n x-enumDescriptions:\n input_filters: Evaluate routing criteria using declarative filter expressions.\n script: Evaluate routing criteria using a custom JavaScript function.\n examples: [\"input_filters\"]\n x-celigo-ai-guidance:\n - Set this and `routeRecordsTo` together on every router that decides between branches, and leave both unset on a one-branch pass-through router; the rejections for a half-set pair or a multi-branch pass-through are listed on `routeRecordsTo`.\n script:\n type: object\n description: |\n Script configuration when routeRecordsUsing=\"script\".\n\n Enables custom routing logic that can't be expressed with filters.\n properties:\n _scriptId:\n type: string\n format: objectId\n x-celigo-refModel: scripts\n description: Reference to the script resource\n examples: [\"60a2c4e6f321d800129a1a3c\"]\n function:\n type: string\n description: Function name that returns the branch name for each record\n examples: [\"routeByCustomerType\", \"calculateBranch\"]\n branches:\n type: array\n description: |\n List of branches that define different processing paths.\n\n Each branch has its own criteria and processing steps. Records are evaluated\n against branch criteria in the order defined.\n items:\n $ref: \"./branch.yml#/Branch\"\n required:\n - id\n - branches\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":4797,"content_sha256":"9b6aa93dfea65014fec5b099d8990b1612fb4118c71ada5a5b152c48bc9c56b9"}],"content_json":{"type":"doc","content":[{"type":"paragraph","content":[{"text":"\u003c!-- TIER:1 -->","type":"text"}]},{"type":"heading","attrs":{"level":1},"content":[{"text":"Building Flows","type":"text"}]},{"type":"paragraph","content":[{"text":"A flow moves data from one or more source systems to one or more destination systems. It runs on a schedule, in response to events (webhooks, listeners), or when triggered by another flow. Flows are the primary way integrations get work done in Celigo.","type":"text"}]},{"type":"paragraph","content":[{"text":"A flow has ","type":"text"},{"text":"page generators","type":"text","marks":[{"type":"strong"}]},{"text":" (exports that fetch data) and ","type":"text"},{"text":"page processors","type":"text","marks":[{"type":"strong"}]},{"text":" (imports and lookups that process each record). Processors run sequentially in a flat list, or conditionally through routers that branch records to different paths. These processing pipeline mechanics -- routers, branches, page processors, response mapping -- are shared with APIs and tools (see ","type":"text"},{"text":"building-apis","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"building-tools","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"How to Run the Steps","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill names each build step by what it reads or changes; the exact calls depend on how you reach the account. Read the one reference that matches your tools -- both follow the step numbers of How to Build a Flow below, then Duplicate Records and the headings of Flow Operations:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Celigo MCP tools","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"list_flows","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"upsert_flow","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"patch_flow","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"run_flow","type":"text","marks":[{"type":"code_inline"}]},{"text":", ...) -- ","type":"text"},{"text":"references/mcp.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/mcp.md","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The ","type":"text","marks":[{"type":"strong"}]},{"text":"celigo","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" CLI","type":"text","marks":[{"type":"strong"}]},{"text":" -- ","type":"text"},{"text":"references/cli.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/cli.md","title":null}}]}]}]}]},{"type":"paragraph","content":[{"text":"Planning and checking what already exists only read the account. Creating or changing a resource, enabling a flow, running it, and retrying or resolving its errors all write to the account or to the connected systems. The two surfaces differ most on writes -- which fields can be changed on their own, and whether the read-modify-write of a full update is done for you -- so follow your reference there.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"What Starts a Flow","type":"text"}]},{"type":"paragraph","content":[{"text":"Flows ","type":"text"},{"text":"start themselves","type":"text","marks":[{"type":"strong"}]},{"text":" -- this is the biggest thing that separates them from APIs (invoked by an HTTP caller) and tools (invoked by a consumer). Every flow begins with one or more page generators, of two kinds:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Scheduled exports","type":"text","marks":[{"type":"strong"}]},{"text":" -- the flow runs on a cron cadence and each run pulls from the source: everything (full sync), only what changed since the last run (delta sync), records matching a query, or files landed in an FTP/SFTP/S3 folder. The right primitive for batch work: nightly reconciliations, hourly delta syncs, backfills, off-peak windows.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Listeners","type":"text","marks":[{"type":"strong"}]},{"text":" -- the source pushes to the flow. A webhook fires (or a NetSuite/Salesforce native real-time event triggers) and the payload immediately starts flowing. No schedule; the flow runs as events arrive. The right primitive for event-driven work (\"when X happens, do Y\"), especially when latency matters.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"A flow can mix both, and multi-generator designs are common:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Real-time plus batch safety net","type":"text","marks":[{"type":"strong"}]},{"text":" -- a listener catches events as they fire; a scheduled export reconciles at off-peak hours, catching up after webhook outages","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Consolidating sources","type":"text","marks":[{"type":"strong"}]},{"text":" -- customers from Salesforce AND HubSpot, each with its own generator, feeding the same downstream pipeline","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Different slices of the same source","type":"text","marks":[{"type":"strong"}]},{"text":" -- one export pulls new records, another pulls updated records, when the API exposes them separately","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"If the requirement is \"every night at 2 AM, do X\" or \"when a webhook arrives, do Y\" -- that lives on a flow. APIs and tools have no schedule and no listener; they only run when invoked.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Describing a Flow","type":"text"}]},{"type":"paragraph","content":[{"text":"Asked to explain, document or summarize what an integration or a flow does, read each flow's document and its steps -- the flow list doesn't show the sources' schedules, the routing or the filters -- and change nothing. For each flow, say:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When each source runs.","type":"text","marks":[{"type":"strong"}]},{"text":" A page generator's own ","type":"text"},{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]},{"text":" replaces the flow's for that source, and a generator without one runs on the flow's, so one flow can run a source hourly and another nightly: read ","type":"text"},{"text":"pageGenerators[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" before you say when a flow runs. Both are read in the flow's ","type":"text"},{"text":"timezone","type":"text","marks":[{"type":"code_inline"}]},{"text":". A flow with none runs in the account owner's profile time zone, and in UTC when the owner has none. Give each time with its zone.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"What starts a flow with no schedule.","type":"text","marks":[{"type":"strong"}]},{"text":" A listener runs when a sender posts to it, and a flow named in another flow's ","type":"text"},{"text":"_runNextFlowIds","type":"text","marks":[{"type":"code_inline"}]},{"text":" runs when that flow's run completes. A disabled flow doesn't run at all. The runs agree: ","type":"text"},{"text":"triggeredBy","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":"system","type":"text","marks":[{"type":"code_inline"}]},{"text":" on a scheduled run, the upstream flow's ","type":"text"},{"text":"_id","type":"text","marks":[{"type":"code_inline"}]},{"text":" on a chained one, and absent on a listener's.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Where records go.","type":"text","marks":[{"type":"strong"}]},{"text":" Walk the routers from ","type":"text"},{"text":"routers[0]","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"Branching Flows","type":"text","marks":[{"type":"link","attrs":{"href":"#branching-flows","title":null}}]},{"text":"). Under ","type":"text"},{"text":"first_matching_branch","type":"text","marks":[{"type":"code_inline"}]},{"text":" a record takes only the first branch, in order, whose filter it matches, so say where a record that matches several goes; under ","type":"text"},{"text":"all_matching_branches","type":"text","marks":[{"type":"code_inline"}]},{"text":" it takes every one.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"What's skipped.","type":"text","marks":[{"type":"strong"}]},{"text":" An export's output ","type":"text"},{"text":"filter","type":"text","marks":[{"type":"code_inline"}]},{"text":" drops the records it doesn't match without an error (the run counts them as ignored), and an import's filter does the same at its step; a record no branch takes leaves with no error and no count.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Fetched Data Needs a Downstream Consumer","type":"text"}]},{"type":"paragraph","content":[{"text":"A common design mistake: ending a flow on an import that ","type":"text"},{"text":"fetches","type":"text","marks":[{"type":"em"}]},{"text":" data back from a remote system (a preview call, a query, a lookup-shaped POST) and relying on response mapping to capture the result. Response mapping makes fields visible to the NEXT step -- if no next step exists, the captured data is discarded when the run ends and nobody sees it.","type":"text"}]},{"type":"paragraph","content":[{"text":"When the requirement says \"preview / estimate / retrieve / fetch / check / look up\", the design needs at least one of:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A write-back import","type":"text","marks":[{"type":"strong"}]},{"text":" to the source system (most common) -- e.g. source export -> preview import -> update import that writes the captured fields onto the source record","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A persistent destination","type":"text","marks":[{"type":"strong"}]},{"text":" the user named (file to S3/SFTP, email, database)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A router or AI agent step","type":"text","marks":[{"type":"strong"}]},{"text":" that consumes the captured data within the same run","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"A two-step export -> fetch-shaped-import flow with nothing after it is a smell -- re-read the intent for where the fetched data should end up. The same applies in reverse: capturing a created record's ID via response mapping is only useful if a later step writes it somewhere.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Flow Topologies","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Linear Flows","type":"text"}]},{"type":"paragraph","content":[{"text":"A flat ","type":"text"},{"text":"pageProcessors[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" list with no routers. One or more page generators feed records through a sequential chain of page processors. Each processor is either an import (","type":"text"},{"text":"type: \"import\"","type":"text","marks":[{"type":"code_inline"}]},{"text":") or a lookup export (","type":"text"},{"text":"type: \"export\"","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Records pass through every step in order. Unique to flows -- APIs and tools always use routers.","type":"text"}]},{"type":"paragraph","content":[{"text":"Older flows -- and Data Loader flows, which still produce it -- can carry the legacy two-step shape instead: a top-level ","type":"text"},{"text":"_exportId","type":"text","marks":[{"type":"code_inline"}]},{"text":" (the one source) and ","type":"text"},{"text":"_importId","type":"text","marks":[{"type":"code_inline"}]},{"text":" (the one destination) in place of ","type":"text"},{"text":"pageGenerators","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"pageProcessors","type":"text","marks":[{"type":"code_inline"}]},{"text":". The platform still accepts and serves it; read such a flow's steps from those two fields, and build new flows with ","type":"text"},{"text":"pageGenerators","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"pageProcessors","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Branching Flows","type":"text"}]},{"type":"paragraph","content":[{"text":"Page generators feed records into ","type":"text"},{"text":"routers[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" instead of ","type":"text"},{"text":"pageProcessors[]","type":"text","marks":[{"type":"code_inline"}]},{"text":". Each router evaluates records against branch conditions and routes them to matching branches. Branches contain their own ","type":"text"},{"text":"pageProcessors[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" and can chain to other routers via ","type":"text"},{"text":"nextRouterId","type":"text","marks":[{"type":"code_inline"}]},{"text":". Records enter at ","type":"text"},{"text":"routers[0]","type":"text","marks":[{"type":"code_inline"}]},{"text":" and reach every other router only through a ","type":"text"},{"text":"nextRouterId","type":"text","marks":[{"type":"code_inline"}]},{"text":", so the rest of the array isn't execution order (Flow Builder can save routers out of order): walk the chain from ","type":"text"},{"text":"routers[0]","type":"text","marks":[{"type":"code_inline"}]},{"text":" when you describe or change a branching flow.","type":"text"}]},{"type":"paragraph","content":[{"text":"Two routing modes (shared with APIs and tools):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Input filters","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"routeRecordsUsing: \"input_filters\"","type":"text","marks":[{"type":"code_inline"}]},{"text":") -- S-expression rules on each branch; last branch can omit filter as a catch-all","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Script-based","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"routeRecordsUsing: \"script\"","type":"text","marks":[{"type":"code_inline"}]},{"text":") -- a JavaScript ","type":"text"},{"text":"branching","type":"text","marks":[{"type":"code_inline"}]},{"text":" function returns, per record, an array of branch indices -- zero-based positions in ","type":"text"},{"text":"branches[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" (e.g. ","type":"text"},{"text":"[0]","type":"text","marks":[{"type":"code_inline"}]},{"text":"); ","type":"text"},{"text":"[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" sends the record down no branch","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Flows support both ","type":"text"},{"text":"first_matching_branch","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"all_matching_branches","type":"text","marks":[{"type":"code_inline"}]},{"text":" routing. APIs only support ","type":"text"},{"text":"first_matching_branch","type":"text","marks":[{"type":"code_inline"}]},{"text":". Tools support ","type":"text"},{"text":"first_matching_branch","type":"text","marks":[{"type":"code_inline"}]},{"text":" only.","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"first_matching_branch","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" -- a record takes the first branch that matches, so a broad filter ahead of a narrow one takes its records: order branches from most to least specific. A script that returns more than one index fails that record with ","type":"text"},{"text":"invalid_script_routing","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"all_matching_branches","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" -- a record goes down every branch that matches, each as its own copy.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No match","type":"text","marks":[{"type":"strong"}]},{"text":" -- a record that no branch takes leaves the run with no error and no ignore count. When records go missing, check each branch's filter, or read a test run's ","type":"text"},{"text":"router","type":"text","marks":[{"type":"code_inline"}]},{"text":" stage, which lists the branches each record took.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"A flow uses EITHER ","type":"text"},{"text":"pageProcessors","type":"text","marks":[{"type":"code_inline"}]},{"text":" (linear) OR ","type":"text"},{"text":"routers","type":"text","marks":[{"type":"code_inline"}]},{"text":" (branching) at the top level -- not both. Turning a linear flow into a branching one moves its top-level steps into the branches; a body that keeps them next to ","type":"text"},{"text":"routers","type":"text","marks":[{"type":"code_inline"}]},{"text":" is refused with 422.","type":"text"}]},{"type":"paragraph","content":[{"text":"A branching router sets both ","type":"text"},{"text":"routeRecordsTo","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"routeRecordsUsing","type":"text","marks":[{"type":"code_inline"}]},{"text":"; a router with only one of them is refused with 422. With input filters:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The filter shape.","type":"text","marks":[{"type":"strong"}]},{"text":" Each branch's ","type":"text"},{"text":"inputFilter","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":"{ version: \"1\", rules: [...] }","type":"text","marks":[{"type":"code_inline"}]},{"text":", not the ","type":"text"},{"text":"{ type: \"expression\", expression: {...} }","type":"text","marks":[{"type":"code_inline"}]},{"text":" wrapper that export and import filters use. A wrapped filter saves without an error, but the branch is stored with no filter, and every record goes down it (live-verified 2026-10-21) -- read the flow back and check each branch's ","type":"text"},{"text":"inputFilter","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Wrap every ","type":"text","marks":[{"type":"strong"}]},{"text":"extract","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" in a type conversion -- ","type":"text"},{"text":"string","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"number","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"boolean","type":"text","marks":[{"type":"code_inline"}]},{"text":", or ","type":"text"},{"text":"epochtime","type":"text","marks":[{"type":"code_inline"}]},{"text":". A bare ","type":"text"},{"text":"[\"extract\", \"\u003cfield>\"]","type":"text","marks":[{"type":"code_inline"}]},{"text":" is refused with 422.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Order the branches.","type":"text","marks":[{"type":"strong"}]},{"text":" They are checked in order. A branch with no filter matches every record, so under ","type":"text"},{"text":"first_matching_branch","type":"text","marks":[{"type":"code_inline"}]},{"text":" the catch-all goes last; under ","type":"text"},{"text":"all_matching_branches","type":"text","marks":[{"type":"code_inline"}]},{"text":" a catch-all also receives a copy of every record the other branches take.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"A two-branch router, wholesale orders to one import and everything else to another:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"pageGenerators\": [{ \"_exportId\": \"\u003cexportId>\" }],\n \"routers\": [{\n \"id\": \"rT8kQ2mZ4vX\",\n \"routeRecordsTo\": \"first_matching_branch\",\n \"routeRecordsUsing\": \"input_filters\",\n \"branches\": [\n {\n \"name\": \"Wholesale\",\n \"inputFilter\": { \"version\": \"1\", \"rules\": [\"equals\", [\"string\", [\"extract\", \"channel\"]], \"wholesale\"] },\n \"pageProcessors\": [{ \"type\": \"import\", \"_importId\": \"\u003cwholesaleImportId>\" }]\n },\n {\n \"name\": \"Everything else\",\n \"pageProcessors\": [{ \"type\": \"import\", \"_importId\": \"\u003cretailImportId>\" }]\n }\n ]\n }]\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"On an existing flow, this replaces its top-level ","type":"text"},{"text":"pageProcessors","type":"text","marks":[{"type":"code_inline"}]},{"text":" in the full document you send back; every other field goes back as read.","type":"text"}]},{"type":"paragraph","content":[{"text":"When a branching flow needs linear steps before the branch point (e.g., a lookup enrichment or AI classification that all branches depend on), use a ","type":"text"},{"text":"pass-through router","type":"text","marks":[{"type":"strong"}]},{"text":": a single-branch router with ","type":"text"},{"text":"nextRouterId","type":"text","marks":[{"type":"code_inline"}]},{"text":" pointing to the branching router. Omit ","type":"text"},{"text":"routeRecordsTo","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"routeRecordsUsing","type":"text","marks":[{"type":"code_inline"}]},{"text":" on the pass-through router -- including them makes it appear as a filter-based branch in the UI. Leaving both unset is the pass-through shape; the server fills in no default. Setting only one of the two is refused (422 ","type":"text"},{"text":"invalid_flow_router_config","type":"text","marks":[{"type":"code_inline"}]},{"text":"), and so is a pass-through router with more than one branch (422 ","type":"text"},{"text":"invalid_flow_router_branch","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]},{"type":"paragraph","content":[{"text":"The same shape rejoins branches: point every branch's ","type":"text"},{"text":"nextRouterId","type":"text","marks":[{"type":"code_inline"}]},{"text":" at one pass-through router and put the shared steps in its branch. Don't repeat a step in each branch instead -- an import can be a step only once per flow, and a second entry fails with 422 ","type":"text"},{"text":"invalid_flow","type":"text","marks":[{"type":"code_inline"}]},{"text":" (\"already included as a pageProcessor\").","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Abstract / Instance Flows","type":"text"}]},{"type":"paragraph","content":[{"text":"A template/inheritance model. An abstract flow (","type":"text"},{"text":"isAbstract: true","type":"text","marks":[{"type":"code_inline"}]},{"text":") defines the complete graph but cannot execute. Instance flows (","type":"text"},{"text":"_abstractFlowId","type":"text","marks":[{"type":"code_inline"}]},{"text":") inherit the graph and customize via an ","type":"text"},{"text":"overrides","type":"text","marks":[{"type":"code_inline"}]},{"text":" object (connections, schedules, mappings, filters).","type":"text"}]},{"type":"paragraph","content":[{"text":"Use when the same flow structure is deployed across multiple regions, tenants, or environments with different connections or parameters.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Reference","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Flow Type Decision Matrix","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pattern","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Structure","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Key fields","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Read schema","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Linear","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Flat processor list","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pageGenerators[]","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"pageProcessors[]","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"request.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"page-generator.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"page-processor.yml","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Branching (routers)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Routers with conditional branches","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pageGenerators[]","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"routers[]","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"+ ","type":"text"},{"text":"router.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"branch.yml","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Abstract / Instance","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Template + per-instance overrides","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"isAbstract: true","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"_abstractFlowId","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"overrides","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"+ ","type":"text"},{"text":"overrides-helper.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"overrides.yml","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Minimum Required Fields","type":"text"}]},{"type":"paragraph","content":[{"text":"Every flow needs at minimum:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"name","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- display name","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"_integrationId","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- parent integration. Without one the flow lands in the account's \"Standalone flows\" integration (or the create fails with 422 ","type":"text"},{"text":"standalone_flows_not_supported","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"disabled: true","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- always create disabled","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"pageGenerators[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- at least one entry with ","type":"text"},{"text":"_exportId","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Either","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"pageProcessors[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" (linear) ","type":"text"},{"text":"or","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"routers[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" (branching) -- never both","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Which Schemas to Read","type":"text"}]},{"type":"paragraph","content":[{"text":"Always read:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"request.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/request.yml","title":null}}]},{"text":" -- base flow fields","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"page-generator.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/page-generator.yml","title":null}}]},{"text":" -- export sources, per-generator schedules, delta coordination","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"page-processor.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/page-processor.yml","title":null}}]},{"text":" -- import/export steps with responseMapping and hooks","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Add for branching flows:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"router.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/router.yml","title":null}}]},{"text":" -- routing strategy, record distribution mode","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"branch.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/branch.yml","title":null}}]},{"text":" -- input filters, per-branch processors, chaining","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Add if response mapping is needed:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"response-mapping.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/response-mapping.yml","title":null}}]},{"text":" -- extract/generate pairs for carrying data between steps","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"All available schemas","type":"text","marks":[{"type":"strong"}]},{"text":" (in ","type":"text"},{"text":"references/schemas/","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/","title":null}}]},{"text":"):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Base fields (all flows):","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"request.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/request.yml","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Response shape:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"response.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/response.yml","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Page generators:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"page-generator.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/page-generator.yml","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Page processors:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"page-processor.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/page-processor.yml","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Response mapping:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"response-mapping.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/response-mapping.yml","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Routers:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"router.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/router.yml","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Branches:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"branch.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/branch.yml","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Abstract flow helpers:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"overrides-helper.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/overrides-helper.yml","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Instance overrides:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"overrides.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/overrides.yml","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cloning:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"clone-request.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/clone-request.yml","title":null}}]},{"text":", ","type":"text"},{"text":"clone-response.yml","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas/clone-response.yml","title":null}}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Related Skills","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"configuring-connections","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"Quick Reference","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-connections/SKILL.md#quick-reference","title":null}}]},{"text":") -- connection types and auth methods for page generators and processors","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"configuring-exports","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"Quick Reference","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-exports/SKILL.md#quick-reference","title":null}}]},{"text":") -- building exports used as page generators and lookup processors","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"configuring-imports","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"Quick Reference","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/SKILL.md#quick-reference","title":null}}]},{"text":") -- building imports used as page processors","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"configuring-topics","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"How to Build an Event Stream","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-topics/SKILL.md#how-to-build-an-event-stream","title":null}}]},{"text":") -- Event Streams topics: one flow publishes records, listener flows consume them","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"writing-mappings","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"Mapper 2.0 Workflow","type":"text","marks":[{"type":"link","attrs":{"href":"../writing-mappings/SKILL.md#mapper-20-workflow","title":null}}]},{"text":") -- field mappings on imports and response mapping between steps","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"writing-scripts","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"Data Pipeline Hooks","type":"text","marks":[{"type":"link","attrs":{"href":"../writing-scripts/SKILL.md#data-pipeline-hooks","title":null}}]},{"text":") -- preSavePage, preMap, postMap, postSubmit, postResponseMap hooks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"writing-handlebars","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"Quick Reference","type":"text","marks":[{"type":"link","attrs":{"href":"../writing-handlebars/SKILL.md#quick-reference","title":null}}]},{"text":") -- dynamic values in URIs, filters, delta tokens, SQL queries","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"troubleshooting-flows","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"Diagnostic Workflow","type":"text","marks":[{"type":"link","attrs":{"href":"../troubleshooting-flows/SKILL.md#diagnostic-workflow","title":null}}]},{"text":") -- diagnosing flow failures, errors, and performance issues","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"building-syncs","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"Sync or Flow?","type":"text","marks":[{"type":"link","attrs":{"href":"../building-syncs/SKILL.md#sync-or-flow","title":null}}]},{"text":") -- replicating many application tables into a data warehouse, which is a sync's job, not a flow's","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"\u003c!-- TIER:2 -->","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"How to Build a Flow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. Plan the flow","type":"text"}]},{"type":"paragraph","content":[{"text":"Before creating anything, decide what kind of operation this is:","type":"text"}]},{"type":"paragraph","content":[{"text":"Decision tree:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Modifying an existing flow's step config","type":"text","marks":[{"type":"strong"}]},{"text":" (export settings, import mappings, scripts) -- first find every flow that uses the step: read the account's flows with their steps and look for the step's ","type":"text"},{"text":"_id","type":"text","marks":[{"type":"code_inline"}]},{"text":" in each one's page generators, page processors and branches (or list the step's dependents, where your surface can). An export or import can be a step in several flows, and an edit to it changes every one of them, so a change asked for one flow can break another. If another flow uses it, stop before any write: tell the user which flows share the step and let them choose -- change it for all of them, give this flow its own copy, or narrow only this flow with a filter on its own import or branch. Then work on the step directly, not the flow: change the export or import itself, or follow the relevant skill (configuring-exports, configuring-imports, writing-scripts, writing-mappings)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Modifying an existing flow's structure","type":"text","marks":[{"type":"strong"}]},{"text":" (add/remove steps, change schedule, rename) -- read the flow, modify the structure, and write the complete document back. Don't rebuild from scratch. Only a few single fields (the name, enable/disable, the flow-level schedule and timezone) can be changed on their own -- your reference lists them. A page generator's own ","type":"text"},{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]},{"text":" isn't one of them and overrides the flow's, so read ","type":"text"},{"text":"pageGenerators[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" before changing when a flow runs (step 8)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Modifying an Integration App's flow","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"_connectorId","type":"text","marks":[{"type":"code_inline"}]},{"text":" set) -- edit only what the app leaves open, such as import mappings, lookups, input filters, and response mappings. Its steps, each step's connection, the endpoints and hooks the app ships, and some data-shaping settings are locked, and a full update that changes a locked field still answers 200 and keeps the app's value -- read the flow or step back after every change. For anything else, use the app's settings or build a companion flow outside the app (","type":"text"},{"text":"using-marketplace-templates > Changing an Installed Integration App","type":"text","marks":[{"type":"link","attrs":{"href":"../using-marketplace-templates/SKILL.md#changing-an-installed-integration-app","title":null}}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Building a new flow where every step is known","type":"text","marks":[{"type":"strong"}]},{"text":" -- build directly, bottom-up (skip to step 2)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Any ambiguity about what steps are needed","type":"text","marks":[{"type":"strong"}]},{"text":" -- design first. List every system, every data direction, every step before writing any JSON","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Design checklist (when ambiguity exists):","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"What source systems? What destination systems?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"What data moves between them, in which direction?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"How often? (cron schedule, webhook trigger, on-demand)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"What happens when a step fails? (","type":"text"},{"text":"proceedOnFailure","type":"text","marks":[{"type":"code_inline"}]},{"text":", error notifications)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Do downstream steps need data from upstream responses? (response mapping)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Is this a one-off or a reusable template? (abstract/instance flow)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Production or a sandbox? Each environment has its own connections and flows, reached with its own token or profile, and nothing crosses between them. Never set the deprecated ","type":"text"},{"text":"sandbox","type":"text","marks":[{"type":"code_inline"}]},{"text":" field to move or classify a flow (","type":"text"},{"text":"getting-started > Sandbox vs Production","type":"text","marks":[{"type":"link","attrs":{"href":"../getting-started/SKILL.md#sandbox-vs-production","title":null}}]},{"text":")","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Identify the integration","type":"text"}]},{"type":"paragraph","content":[{"text":"Every flow belongs to an integration (the container). Find or create the integration first: list the account's integrations and pick the one this flow belongs to.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. Check for existing patterns","type":"text"}]},{"type":"paragraph","content":[{"text":"Before building from scratch, check what already exists in the account and marketplace:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Search the account for similar resources by keyword","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Show what an existing resource uses and what uses it","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Find orphaned resources, offline connections, and untriggered flows","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Search the marketplace for pre-built integration templates, and preview a template before installing it","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. Build the connections, exports, and imports","type":"text"}]},{"type":"paragraph","content":[{"text":"Flows reference existing resources. Build bottom-up: connections first, then exports and imports that use those connections, then the flow that wires them together. List the account's connections, exports, and imports first to see what already exists.","type":"text"}]},{"type":"paragraph","content":[{"text":"For every step, match the adaptor to the target application -- raw HTTP is the fallback, not the default. Use the native adaptor when one exists (NetSuite, Salesforce, databases, FTP/S3); otherwise check the HTTP connector catalog for a pre-built connector (550+ apps) and build the connection from it; hand-write HTTP config from public API docs only when no connector exists or it doesn't cover the endpoint. See ","type":"text"},{"text":"configuring-exports","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"Check for a pre-built connector","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-exports/SKILL.md#3-check-for-a-pre-built-connector","title":null}}]},{"text":") and ","type":"text"},{"text":"configuring-imports","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"Check for a pre-built connector","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/SKILL.md#3-check-for-a-pre-built-connector","title":null}}]},{"text":").","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"configuring-exports","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"configuring-imports","type":"text","marks":[{"type":"code_inline"}]},{"text":" for how to build each resource.","type":"text"}]},{"type":"paragraph","content":[{"text":"A source that should read only what changed since the last run is a delta export: set ","type":"text"},{"text":"type: \"delta\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" on it, with ","type":"text"},{"text":"{{lastExportDateTime}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" in an HTTP or SQL request (","type":"text"},{"text":"configuring-exports > Delta vs one-time vs full sync","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-exports/SKILL.md#delta-vs-one-time-vs-full-sync","title":null}}]},{"text":"). On an export with no ","type":"text"},{"text":"type","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"{{lastExportDateTime}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" renders empty on every run, so the request goes out with a blank bound each time.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"5. Choose the topology","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Scenario","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Topology","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"All records follow the same path","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Linear (","type":"text"},{"text":"pageProcessors","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Records need conditional routing by field values","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Branching with input filters","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Routing logic requires custom JavaScript","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Branching with script router","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Records should fan out to all matching paths","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Branching with ","type":"text"},{"text":"all_matching_branches","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Same structure across multiple tenants/regions","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Abstract + instance flows","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Abstract/instance flows:","type":"text","marks":[{"type":"strong"}]},{"text":" Abstract flows are reusable templates that cannot run directly. Instance flows inherit the abstract's structure and override specific fields (connections, filters, schedules). Use when the same integration pattern repeats across tenants or regions. Create with ","type":"text"},{"text":"isAbstract: true","type":"text","marks":[{"type":"code_inline"}]},{"text":". An abstract flow can't use top-level ","type":"text"},{"text":"pageProcessors","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- the platform rejects them with 422 ","type":"text"},{"text":"abstract_flow_invalid_fields","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- so put even a linear chain in ","type":"text"},{"text":"routers[]","type":"text","marks":[{"type":"code_inline"}]},{"text":", as one ","type":"text"},{"text":"pass-through router","type":"text","marks":[{"type":"link","attrs":{"href":"#branching-flows","title":null}}]},{"text":" (a single branch, neither ","type":"text"},{"text":"routeRecordsTo","type":"text","marks":[{"type":"code_inline"}]},{"text":" nor ","type":"text"},{"text":"routeRecordsUsing","type":"text","marks":[{"type":"code_inline"}]},{"text":" set). An abstract flow can't reference another abstract flow or be a run-next target, and ","type":"text"},{"text":"isAbstract","type":"text","marks":[{"type":"code_inline"}]},{"text":" can't be cleared while instances exist. Instance flows reference the abstract via ","type":"text"},{"text":"_abstractFlowId","type":"text","marks":[{"type":"code_inline"}]},{"text":" and specify overrides -- they do NOT use the normal scaffolding process. ","type":"text"},{"text":"_integrationId","type":"text","marks":[{"type":"code_inline"}]},{"text":" is NOT inherited and must be set explicitly on the instance.","type":"text"}]},{"type":"paragraph","content":[{"text":"Instances that keep the abstract flow's webhook listener share its URL, so one request runs every enabled instance. To give an instance its own URL, create a separate webhook export and swap it in on that instance with ","type":"text"},{"text":"overrides.exports: [{\"_abstractId\": \"\u003cabstract listener>\", \"_id\": \"\u003cnew export>\"}]","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- exports are the only steps an instance can replace, and an entry with only ","type":"text"},{"text":"_abstractId","type":"text","marks":[{"type":"code_inline"}]},{"text":" is refused with 422.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"6. Design the step sequence","type":"text"}]},{"type":"paragraph","content":[{"text":"For each step, decide:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Type","type":"text","marks":[{"type":"strong"}]},{"text":" -- ","type":"text"},{"text":"import","type":"text","marks":[{"type":"code_inline"}]},{"text":" (write to destination) or ","type":"text"},{"text":"export","type":"text","marks":[{"type":"code_inline"}]},{"text":" (lookup for enrichment). A Celigo Tool runs as an import step (","type":"text"},{"text":"adaptorType: \"ToolImport\"","type":"text","marks":[{"type":"code_inline"}]},{"text":") that must bind every connection the Tool's steps use, or its run fails with ","type":"text"},{"text":"connection_not_overridden","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"configuring-imports > Stack and Tool Imports","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/SKILL.md#stack-and-tool-imports","title":null}}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Response mapping","type":"text","marks":[{"type":"strong"}]},{"text":" -- what does a downstream step need from this step's response? A response reaches the record only through the step's response mapping (or a ","type":"text"},{"text":"postResponseMap","type":"text","marks":[{"type":"code_inline"}]},{"text":" hook); without one it's discarded after the call, even when its field names already match what the next step expects. Every lookup whose results are used downstream needs one; skip it only when nothing downstream reads this step's response (often a terminal import). An import's own mapping and its preMap/postMap hooks build only the request it sends: the next step gets the record as it arrived, plus what response mapping merges.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"proceedOnFailure","type":"text","marks":[{"type":"strong"}]},{"text":" -- should the pipeline continue if this step fails?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Hooks","type":"text","marks":[{"type":"strong"}]},{"text":" -- does this step need a ","type":"text"},{"text":"postResponseMap","type":"text","marks":[{"type":"code_inline"}]},{"text":" script?","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"7. Build the flow JSON","type":"text"}]},{"type":"paragraph","content":[{"text":"Reference the schemas listed in the Quick Reference above for exact field schemas. Every step in the document runs -- there's no comment or placeholder step -- so note planned work in the flow's ","type":"text"},{"text":"description","type":"text","marks":[{"type":"code_inline"}]},{"text":" (up to 5,120 characters), not as a dummy step.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"8. Configure scheduling","type":"text"}]},{"type":"paragraph","content":[{"text":"Pair ","type":"text"},{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]},{"text":" (6-field cron) with ","type":"text"},{"text":"timezone","type":"text","marks":[{"type":"code_inline"}]},{"text":" (IANA). Omit both for listener/webhook/realtime flows. A flow with no ","type":"text"},{"text":"timezone","type":"text","marks":[{"type":"code_inline"}]},{"text":" runs in the account owner's profile time zone, or in UTC when the owner has none, so set the zone the schedule means (\"9 AM every weekday\" should survive daylight saving).","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Write local times as they are.","type":"text","marks":[{"type":"strong"}]},{"text":" The schedule's hours are read in the flow's ","type":"text"},{"text":"timezone","type":"text","marks":[{"type":"code_inline"}]},{"text":", so set the user's zone and keep their hours. Never convert them to UTC: a converted schedule in a zone with daylight saving is an hour off for half the year, and paired with the local ","type":"text"},{"text":"timezone","type":"text","marks":[{"type":"code_inline"}]},{"text":" it is shifted twice.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Day-of-week counts 0=Sunday to 6=Saturday","type":"text","marks":[{"type":"strong"}]},{"text":", so weekdays are ","type":"text"},{"text":"1-5","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or ","type":"text"},{"text":"MON-FRI","type":"text","marks":[{"type":"code_inline"}]},{"text":"): ","type":"text"},{"text":"\"? 5 8,17 ? * 1-5\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" runs at 8:05 AM and 5:05 PM, Monday to Friday. ","type":"text"},{"text":"2-6","type":"text","marks":[{"type":"code_inline"}]},{"text":" is Tuesday to Saturday.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check the page generators first.","type":"text","marks":[{"type":"strong"}]},{"text":" A page generator can carry its own ","type":"text"},{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]},{"text":", which overrides the flow's for that generator (e.g. one source syncs hourly, another nightly, in the same flow); a generator without one inherits the flow's. Before changing or describing when a flow runs, read its ","type":"text"},{"text":"pageGenerators[]","type":"text","marks":[{"type":"code_inline"}]},{"text":": a generator with its own ","type":"text"},{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]},{"text":" runs on it, and a change has to be made there, because a new flow-level schedule doesn't move it. A generator has no timezone of its own -- its schedule is read in the flow's ","type":"text"},{"text":"timezone","type":"text","marks":[{"type":"code_inline"}]},{"text":". The flow-level ","type":"text"},{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"timezone","type":"text","marks":[{"type":"code_inline"}]},{"text":" can be changed on their own, but a generator's ","type":"text"},{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]},{"text":" can't: changing it is a full update of the flow.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Lists and step forms are both valid, and a save can move either five minutes later.","type":"text","marks":[{"type":"strong"}]},{"text":" The platform stores the minute field 5 minutes later when it is 0, 15, 30 or 45, a list of only those minutes, a range from minute 0, or a ","type":"text"},{"text":"*/N","type":"text","marks":[{"type":"code_inline"}]},{"text":" step above 5, and the stored schedule is what runs: ","type":"text"},{"text":"? 0 6 ? * *","type":"text","marks":[{"type":"code_inline"}]},{"text":" (6:00) is stored as ","type":"text"},{"text":"? 5 6 ? * *","type":"text","marks":[{"type":"code_inline"}]},{"text":" and starts at 6:05, ","type":"text"},{"text":"? 0,15,30,45 * ? * *","type":"text","marks":[{"type":"code_inline"}]},{"text":" as ","type":"text"},{"text":"? 5,20,35,50 * ? * *","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"? */15 * ? * *","type":"text","marks":[{"type":"code_inline"}]},{"text":" as ","type":"text"},{"text":"? 5-59/15 * ? * *","type":"text","marks":[{"type":"code_inline"}]},{"text":". Other minute fields are kept as sent, such as ","type":"text"},{"text":"? 5 0,4,8,12,16,20 ? * *","type":"text","marks":[{"type":"code_inline"}]},{"text":" (every four hours at :05) and ","type":"text"},{"text":"? */5 * * * *","type":"text","marks":[{"type":"code_inline"}]},{"text":". Read ","type":"text"},{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]},{"text":" back after saving, and give the user the stored times.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A start during a run is skipped.","type":"text","marks":[{"type":"strong"}]},{"text":" A scheduled start that comes while a run is still in progress is skipped, not queued. Never rewrite a working schedule into another format to fix missed runs.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Every write checks the schedule","type":"text","marks":[{"type":"strong"}]},{"text":", a disabled flow's included: fix or clear a rejected one -- disabling the flow doesn't get past the error.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"9. Set the runtime controls","type":"text"}]},{"type":"paragraph","content":[{"text":"Flow-level and per-step switches that change production behavior. APIs and tools have none of these -- they are flow-only.","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Control","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Where","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Default","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Flip it when","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"proceedOnFailure","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"per processor","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"false","type":"text","marks":[{"type":"code_inline"}]},{"text":" (a failed record stops there)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The step is non-critical and downstream steps still do meaningful work without it (a Slack notification late in the flow shouldn't block the sync). Keep ","type":"text"},{"text":"false","type":"text","marks":[{"type":"code_inline"}]},{"text":" when downstream depends on this step's output. A lookup that finds nothing hasn't failed: the record continues with nothing merged whatever this is set to -- to stop it, make the lookup fail on no match (","type":"text"},{"text":"configuring-exports > Lookup export vs separate scheduled export","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-exports/SKILL.md#lookup-export-vs-separate-scheduled-export","title":null}}]},{"text":")","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"skipRetries","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"per page generator (the export has its own, shown as \"Do not store retry data\")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"false","type":"text","marks":[{"type":"code_inline"}]},{"text":" (failed records keep their retry data and can be retried)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Work is time-sensitive (retrying a stale webhook is meaningless) -- set it on that generator only. It isn't a duplicate fix -- make the write idempotent instead (","type":"text"},{"text":"configuring-imports > Writing Exactly Once","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/SKILL.md#writing-exactly-once","title":null}}]},{"text":")","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"runPageGeneratorsInParallel","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"flow","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"false","type":"text","marks":[{"type":"code_inline"}]},{"text":" (generators run sequentially)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sources are independent and can take the load. Careful: parallel generators hitting the same API can blow rate limits that sequential runs respect","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"autoResolveMatchingTraceKeys","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"flow","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"true","type":"text","marks":[{"type":"code_inline"}]},{"text":" on every new flow: a new error for a record auto-resolves that record's older open errors on the step, a later success resolves them all, and errors classified ","type":"text"},{"text":"intermittent","type":"text","marks":[{"type":"code_inline"}]},{"text":" retry automatically (up to four attempts, the first about 30 minutes later)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rarely. Off, every failure of a record stays open until someone resolves it, and intermittent errors get no automatic retry. Repeated trace keys are normal (delta re-emits, retries) and never raise an error -- a ","type":"text"},{"text":"duplicate","type":"text","marks":[{"type":"code_inline"}]},{"text":" classification means the destination already holds the record, so turning this off doesn't fix it","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"logging.mode","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"flow -- only a patch changes it: creates and full updates ignore ","type":"text"},{"text":"logging","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"accountLevel","type":"text","marks":[{"type":"code_inline"}]},{"text":" (the account's logging setting)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Every run of this flow should keep a per-record log without a debug window. ","type":"text"},{"text":"basic","type":"text","marks":[{"type":"code_inline"}]},{"text":" keeps each record's step status and each HTTP call's method, URL and status code (nothing while the account's logging preference is off); ","type":"text"},{"text":"detailed","type":"text","marks":[{"type":"code_inline"}]},{"text":" adds the payloads and needs payload storage on the account and a plan whose ","type":"text"},{"text":"maxLogMode","type":"text","marks":[{"type":"code_inline"}]},{"text":" allows it -- otherwise arm a time-boxed debug window (","type":"text"},{"text":"troubleshooting-flows","type":"text","marks":[{"type":"link","attrs":{"href":"../troubleshooting-flows/SKILL.md#6-end-to-end-debugging-with-execution-logs-silentlogic-bugs-production-incidents","title":null}}]},{"text":")","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"10. Configure chaining (if needed)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"_runNextFlowIds","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- trigger other flows when this one completes. The classic use is multi-stage pipelines: \"after the customer-master sync finishes, run the orders sync.\" When a requirement says \"X has to happen, ","type":"text"},{"text":"then","type":"text","marks":[{"type":"em"}]},{"text":" Y\", chain two focused flows rather than building one big one. A flow listed here can't be deleted until the reference is removed (422 ","type":"text"},{"text":"dependencies_not_deleted","type":"text","marks":[{"type":"code_inline"}]},{"text":"), and an abstract flow can't be listed","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"_runNextExportIds","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- more granular: trigger specific exports inside other flows instead of the whole flow","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Set the chain on the upstream flow, and add to its list.","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"_runNextFlowIds","type":"text","marks":[{"type":"code_inline"}]},{"text":" lives on the flow that runs first and is a list that may already name other flows: add the new id and keep the entries already there -- writing a one-item list unchains the others. It can't be changed on its own, so it's a full update of the upstream flow","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"What starts the next flow.","type":"text","marks":[{"type":"strong"}]},{"text":" A run that completes starts it, even one that completed with record errors; a run that fails or is canceled doesn't","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Chaining passes no records.","type":"text","marks":[{"type":"strong"}]},{"text":" The next flow starts from its own sources. Hand records between flows through a store, and design that store as a ledger, not a queue -- see ","type":"text"},{"text":"configuring-lookup-caches > Deduplication Ledger","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-lookup-caches/SKILL.md#deduplication-ledger","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A flow runs one instance at a time.","type":"text","marks":[{"type":"strong"}]},{"text":" A run requested while it is queued or running is refused with ","type":"text"},{"text":"409 job_already_queued","type":"text","marks":[{"type":"code_inline"}]},{"text":", and a run-next trigger that arrives then is dropped, not queued -- several parent completions in quick succession start the child once. Listener events are the exception: they join the listener's open run as more pages until about five minutes pass with no new event, and a later event starts a new run, which can still overlap the previous run's last pages. Don't count on one run at a time to keep a listener's writes apart (","type":"text"},{"text":"Duplicate Records","type":"text","marks":[{"type":"link","attrs":{"href":"#duplicate-records","title":null}}]},{"text":")","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"11. Create disabled, test, enable","type":"text"}]},{"type":"paragraph","content":[{"text":"Always create with ","type":"text"},{"text":"disabled: true","type":"text","marks":[{"type":"code_inline"}]},{"text":". Verify the structure by reading the flow back, then test-run it before you enable it. Read each step's per-stage input, output, and errors where your surface offers them.","type":"text"}]},{"type":"paragraph","content":[{"text":"The flow may be enabled or disabled -- an enabled flow's test run answers 200 and runs (live-verified 2026-10-14). A test run is not a dry run of everything:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Imports never send.","type":"text","marks":[{"type":"strong"}]},{"text":" Each returns its saved ","type":"text"},{"text":"mockResponse","type":"text","marks":[{"type":"code_inline"}]},{"text":" (never its ","type":"text"},{"text":"sampleResponseData","type":"text","marks":[{"type":"code_inline"}]},{"text":"), or a placeholder numbered per record -- ","type":"text"},{"text":"{\"id\": \"1234567890-1\", \"mockResponse\": \"replace me\"}","type":"text","marks":[{"type":"code_inline"}]},{"text":", then ","type":"text"},{"text":"-2","type":"text","marks":[{"type":"code_inline"}]},{"text":", and so on -- that the ","type":"text"},{"text":"response","type":"text","marks":[{"type":"code_inline"}]},{"text":" stage shows as a 200 from the endpoint's URL. So response mappings after an import carry nothing real until you save a realistic mock (","type":"text"},{"text":"configuring-imports > Testing an Import Without Writing","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/SKILL.md#testing-an-import-without-writing","title":null}}]},{"text":"), and the import's child job counts every mocked record as a success: its ","type":"text"},{"text":"numSuccess","type":"text","marks":[{"type":"code_inline"}]},{"text":" doesn't mean anything was written. An ","type":"text"},{"text":"id","type":"text","marks":[{"type":"code_inline"}]},{"text":" response mapping merges the mock's ","type":"text"},{"text":"id","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- the saved ","type":"text"},{"text":"mockResponse","type":"text","marks":[{"type":"code_inline"}]},{"text":"'s, or the placeholder's ","type":"text"},{"text":"1234567890-1","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- never the destination's, and postSubmit hooks see the same id.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Exports and lookups read the live source","type":"text","marks":[{"type":"strong"}]},{"text":", unless the export has a saved ","type":"text"},{"text":"mockOutput","type":"text","marks":[{"type":"code_inline"}]},{"text":" (at most 10 records and 1 MB), which is replayed instead (the export's transform and filter still run on it).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Hooks run","type":"text","marks":[{"type":"strong"}]},{"text":", and nothing sandboxes the ","type":"text"},{"text":"integrator-api","type":"text","marks":[{"type":"code_inline"}]},{"text":" module in test mode: treat a hook's writes -- to a lookup cache or state, or an import or flow it runs -- as real.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"One page, and no run history.","type":"text","marks":[{"type":"strong"}]},{"text":" A test run reads only the first page of the generator you pick. It isn't kept with the flow's runs -- reading its job id as a run answers 404 -- so read it from the test-run response and its per-step results.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Enable only after the test run looks right. Enabling is also what switches a NetSuite or topic listener on: its deployment or subscription activates when the flow goes from disabled to enabled, not on a flow created already enabled. After repairing a listener export, or when a NetSuite listener stops firing, disable and re-enable the flow.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Duplicate Records","type":"text"}]},{"type":"paragraph","content":[{"text":"Every source delivers some records more than once, and nothing between the source and the write removes the repeats:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Where the repeat comes from","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Why","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The sender fires again (a webhook retry, a button clicked twice)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A listener never de-duplicates; every request is its own record","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A failed or canceled run is re-run","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The delta checkpoint advances only at the end of a successful run, so the next run re-reads the same window","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A partial failure","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The record wrote one step, failed a later one, and never reached the step that marks it done -- the next run writes the first step again","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Error retries","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"intermittent","type":"text","marks":[{"type":"code_inline"}]},{"text":" errors retry automatically, and manual retries re-send the captured record","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The flow writes back to the records it exports (a status, a processed flag)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The write changes their modified date, so a delta export on that date picks every one up again next run -- add export criteria that skip records already carrying the flow's write, or use a creation-time field","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Another flow writes the same destination","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Checks inside one flow can't see the other","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Repeated lines on one record are another case: a NetSuite update whose sublist has no ","type":"text"},{"text":"isKey","type":"text","marks":[{"type":"code_inline"}]},{"text":" column adds the mapped lines again on every run (","type":"text"},{"text":"writing-mappings > Mapper 1.0","type":"text","marks":[{"type":"link","attrs":{"href":"../writing-mappings/SKILL.md#mapper-10-reference-netsuite-and-salesforce","title":null}}]},{"text":").","type":"text"}]},{"type":"paragraph","content":[{"text":"A trace key never de-duplicates: it correlates a record's errors and is each import's default concurrency-lock key, and a record emitted twice with the same key is processed twice. The fix is an idempotent write -- ","type":"text"},{"text":"configuring-imports > Writing Exactly Once","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/SKILL.md#writing-exactly-once","title":null}}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"Diagnosing \"it created duplicates\" -- read runs before configuration.","type":"text","marks":[{"type":"strong"}]},{"text":" One clean latest run proves nothing.","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"List the flow's runs around the time the user names, and open each one: its per-step child jobs carry each step's ","type":"text"},{"text":"numSuccess","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"numError","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"numIgnore","type":"text","marks":[{"type":"code_inline"}]},{"text":". For one duplicated record, search the flow's runs by its trace key instead: that lists every run that carried it, whatever happened to it there.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The same write step succeeding in two runs while a later step errored is a partial failure; more successes than distinct keys in one run means same-key records reached the write without a lock.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Then the configuration: is the write keyed (a destination-enforced key, or ","type":"text"},{"text":"ignoreExisting","type":"text","marks":[{"type":"code_inline"}]},{"text":" + an existence check) or a bare create? Does ","type":"text"},{"text":"idLockTemplate","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- or the default trace key -- render non-empty for a real record (read a failed record's retry data), not a sample? What marks a record done, and does a failure skip that step?","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Flow Operations","type":"text"}]},{"type":"paragraph","content":[{"text":"The operations on a flow beyond building it. Each reference lists its calls under these same headings and names the ones its surface lacks.","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read and write","type":"text","marks":[{"type":"strong"}]},{"text":" -- list flows, read one and its change history, list what references it, create one, replace a whole flow, change individual fields, delete one. A delete moves the flow to the recycle bin for 30 days, stops its schedule and cancels its running jobs; it's refused while another flow chains to it, and it leaves the flow's exports and imports in place","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run","type":"text","marks":[{"type":"strong"}]},{"text":" -- run on demand, optionally overriding the delta window (a start and end date) or running only some of the page generators; cancel running jobs; alert people when a run takes longer than a threshold","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test run","type":"text","marks":[{"type":"strong"}]},{"text":" -- run the flow in test mode, stage by stage, then read one step's per-stage results","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Clone","type":"text","marks":[{"type":"strong"}]},{"text":" -- copy a flow, with its exports, imports, and scripts, into a target integration and environment, optionally into a flow group. Connections aren't copied: the connection map must name every connection the flow's steps use, each mapped to itself to reuse it or to an existing connection in the target environment","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Change the structure","type":"text","marks":[{"type":"strong"}]},{"text":" -- add a page generator (optionally with its own schedule, at a chosen position) or remove one; add or remove a page processor, at the top level or inside a router branch; replace one connection with another across every step","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Manage errors","type":"text","marks":[{"type":"strong"}]},{"text":" -- list a step's open or resolved errors; resolve, retry, assign (to a user by email) or unassign, or tag them; delete resolved errors; read one error with its retry data and its HTTP request and response; edit an error's retry data before a retry; count open errors per step; group a step's errors by pattern","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Debug","type":"text","marks":[{"type":"strong"}]},{"text":" -- read a step's captured HTTP requests and one request's detail, or delete them; arm and disarm execution logging; read a run's per-record execution logs, one record's stages, and one stage's data; delete the execution logs of a time range","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read the delta checkpoint","type":"text","marks":[{"type":"strong"}]},{"text":" -- the flow's last export date","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Organize with flow groups","type":"text","marks":[{"type":"strong"}]},{"text":" -- list an integration's flow groups, create one, assign flows to it, and remove one: move its flows out first, since a group that still holds flows survives being removed","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"\u003c!-- TIER:3 -->","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Pre-Submit Checklist","type":"text"}]},{"type":"paragraph","content":[{"text":"Before creating or updating a flow, verify:","type":"text"}]},{"type":"checkbox_list","attrs":{"id":null},"content":[{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"_integrationId","type":"text","marks":[{"type":"code_inline"}]},{"text":" references a real integration (confirm by reading the integration by id)","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"disabled: true","type":"text","marks":[{"type":"code_inline"}]},{"text":" is set for initial creation -- an enabled flow with a schedule runs immediately","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]},{"text":" is 6-field cron with seconds: ","type":"text"},{"text":"\"? */5 * * * *\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" (first field is always ","type":"text"},{"text":"?","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"The schedule keeps the user's local hours with their ","type":"text"},{"text":"timezone","type":"text","marks":[{"type":"code_inline"}]},{"text":" (not converted to UTC), weekdays are ","type":"text"},{"text":"1-5","type":"text","marks":[{"type":"code_inline"}]},{"text":", and no page generator has its own ","type":"text"},{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]},{"text":" that should have changed instead","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]},{"text":" was read back after the save, and the times given to the user are the stored ones (minute 0, 15, 30 or 45 can be stored 5 minutes later)","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"pageProcessors[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"routers[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" are mutually exclusive -- a flow uses one or the other, never both","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Every branching router sets both ","type":"text"},{"text":"routeRecordsTo","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"routeRecordsUsing","type":"text","marks":[{"type":"code_inline"}]},{"text":"; each branch ","type":"text"},{"text":"inputFilter","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":"{ version: \"1\", rules: [...] }","type":"text","marks":[{"type":"code_inline"}]},{"text":" with every ","type":"text"},{"text":"extract","type":"text","marks":[{"type":"code_inline"}]},{"text":" inside a type conversion, and a branch with no filter comes last","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Router IDs are unique within the flow (random alphanumeric, e.g., ","type":"text"},{"text":"\"N8Q9NX24Sj5\"","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Every branch ","type":"text"},{"text":"nextRouterId","type":"text","marks":[{"type":"code_inline"}]},{"text":" references an existing router ","type":"text"},{"text":"id","type":"text","marks":[{"type":"code_inline"}]},{"text":" in the same flow","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"_runNextFlowIds","type":"text","marks":[{"type":"code_inline"}]},{"text":" still holds the ids it had, plus the new one","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Before changing an export or import, you found every flow that uses it","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Gotchas","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"PUT erases omitted fields.","type":"text","marks":[{"type":"strong"}]},{"text":" A full update replaces the whole document: always read it first, modify, then write the complete document back. Your reference says which single fields can be changed on their own and whether your surface does the read-modify-write for you.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"pageProcessors","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" and ","type":"text","marks":[{"type":"strong"}]},{"text":"routers","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" are mutually exclusive.","type":"text","marks":[{"type":"strong"}]},{"text":" A flow uses one or the other at the top level. Sending both is refused with 422 ","type":"text"},{"text":"invalid_branched_flow_config","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Router IDs must be unique within a flow.","type":"text","marks":[{"type":"strong"}]},{"text":" Use random alphanumeric strings (e.g., ","type":"text"},{"text":"\"N8Q9NX24Sj5\"","type":"text","marks":[{"type":"code_inline"}]},{"text":"). ","type":"text"},{"text":"nextRouterId","type":"text","marks":[{"type":"code_inline"}]},{"text":" must reference an existing router ","type":"text"},{"text":"id","type":"text","marks":[{"type":"code_inline"}]},{"text":" in the same flow.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create flows with ","type":"text","marks":[{"type":"strong"}]},{"text":"disabled: true","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":".","type":"text","marks":[{"type":"strong"}]},{"text":" An enabled flow with a schedule will run immediately, and a NetSuite or topic listener on a flow created already enabled stays inactive until the flow is disabled and re-enabled. Enable only after verification.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Build order matters.","type":"text","marks":[{"type":"strong"}]},{"text":" Connection -> Export -> Import -> Flow. The API rejects references to non-existent resources.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Schedule is 6-field cron with seconds.","type":"text","marks":[{"type":"strong"}]},{"text":" Format: ","type":"text"},{"text":"\"? minute hour dayOfMonth month dayOfWeek\"","type":"text","marks":[{"type":"code_inline"}]},{"text":". The first field is always ","type":"text"},{"text":"?","type":"text","marks":[{"type":"code_inline"}]},{"text":". Common mistake: using 5-field cron without the seconds position.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Instance flows cannot define structure.","type":"text","marks":[{"type":"strong"}]},{"text":" Do not set ","type":"text"},{"text":"pageGenerators","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"pageProcessors","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"routers","type":"text","marks":[{"type":"code_inline"}]},{"text":", or ","type":"text"},{"text":"runPageGeneratorsInParallel","type":"text","marks":[{"type":"code_inline"}]},{"text":" on instance flows -- these are inherited from the abstract flow, and a write that sets them fails with 422 ","type":"text"},{"text":"flow_instance_invalid_fields","type":"text","marks":[{"type":"code_inline"}]},{"text":". All customizations go through ","type":"text"},{"text":"overrides","type":"text","marks":[{"type":"code_inline"}]},{"text":"; ","type":"text"},{"text":"_abstractFlowId","type":"text","marks":[{"type":"code_inline"}]},{"text":" can't change once set.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Instance flow ","type":"text","marks":[{"type":"strong"}]},{"text":"overrides","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" is full-replace on PUT.","type":"text","marks":[{"type":"strong"}]},{"text":" Omitting an override entry removes it. Always read the instance, merge your changes into its ","type":"text"},{"text":"overrides","type":"text","marks":[{"type":"code_inline"}]},{"text":", then write it back.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Empty ","type":"text","marks":[{"type":"strong"}]},{"text":"pageProcessors: []","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" in a branch is the discard pattern.","type":"text","marks":[{"type":"strong"}]},{"text":" Records matching that branch are dropped -- and so is a record no branch matches, with no error and no ignore count. A branch with no ","type":"text"},{"text":"inputFilter","type":"text","marks":[{"type":"code_inline"}]},{"text":" serves as a catch-all.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"responseMapping","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" uses Transformation 1.0 syntax","type":"text","marks":[{"type":"strong"}]},{"text":" (extract/generate pairs), not expression-based transforms. Lookup export responses use ","type":"text"},{"text":"data[0].fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":"; import responses use ","type":"text"},{"text":"id","type":"text","marks":[{"type":"code_inline"}]},{"text":" for the created record's ID and ","type":"text"},{"text":"_json.fieldName","type":"text","marks":[{"type":"code_inline"}]},{"text":" for response-body fields.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Don't add identity transforms on lookups.","type":"text","marks":[{"type":"strong"}]},{"text":" A lookup's ","type":"text"},{"text":"transform","type":"text","marks":[{"type":"code_inline"}]},{"text":" only reshapes its results (the ","type":"text"},{"text":"data","type":"text","marks":[{"type":"code_inline"}]},{"text":" array response mapping extracts from); it never puts them on the record -- the response mapping does. If the results already have the shape you need, skip the transform and extract directly (","type":"text"},{"text":"data[0].\u003cfield>","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Only add a transform to rename fields, reshape nested structures, or drop fields; an identity transform (e.g., ","type":"text"},{"text":"errorId","type":"text","marks":[{"type":"code_inline"}]},{"text":" -> ","type":"text"},{"text":"$.errorId","type":"text","marks":[{"type":"code_inline"}]},{"text":") adds complexity for no benefit.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Trace keys never de-duplicate.","type":"text","marks":[{"type":"strong"}]},{"text":" A record emitted twice with the same trace key is processed twice; repeated keys (delta re-emits, retries) are normal and raise no error on their own. See ","type":"text"},{"text":"Duplicate Records","type":"text","marks":[{"type":"link","attrs":{"href":"#duplicate-records","title":null}}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A test run isn't side-effect free.","type":"text","marks":[{"type":"strong"}]},{"text":" Imports never send, but exports and lookups read live systems and hooks execute -- treat a hook's ","type":"text"},{"text":"integrator-api","type":"text","marks":[{"type":"code_inline"}]},{"text":" writes as real. Before test-running against production systems, save a ","type":"text"},{"text":"mockResponse","type":"text","marks":[{"type":"code_inline"}]},{"text":" on imports and a ","type":"text"},{"text":"mockOutput","type":"text","marks":[{"type":"code_inline"}]},{"text":" on any lookup you don't want called.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Exports and imports are shared between flows.","type":"text","marks":[{"type":"strong"}]},{"text":" One export can feed several flows, and an edit to it changes what every one of them receives. Before changing a step, find the flows that use it; if another flow does, stop before any write and let the user choose: change it for every flow, give this flow its own copy, or filter on this flow's own steps.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A page generator's own ","type":"text","marks":[{"type":"strong"}]},{"text":"schedule","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" wins.","type":"text","marks":[{"type":"strong"}]},{"text":" A flow-level schedule change answers 200 and leaves a generator that has its own ","type":"text"},{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]},{"text":" running at its old times. Read ","type":"text"},{"text":"pageGenerators[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" before you reschedule a flow or say when it runs.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"A branch filter in the export-filter wrapper is dropped.","type":"text","marks":[{"type":"strong"}]},{"text":" A branch ","type":"text"},{"text":"inputFilter","type":"text","marks":[{"type":"code_inline"}]},{"text":" sent as ","type":"text"},{"text":"{ type: \"expression\", expression: {...} }","type":"text","marks":[{"type":"code_inline"}]},{"text":" saves without an error and is stored as no filter, so the branch takes every record. Send ","type":"text"},{"text":"{ version: \"1\", rules: [...] }","type":"text","marks":[{"type":"code_inline"}]},{"text":" and read the flow back.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"data[0]","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" is whichever result came back first.","type":"text","marks":[{"type":"strong"}]},{"text":" Feed a lookup's first result into an update only when the lookup key is unique (an external id, a primary key): on a name or an email several records can match, and the update lands on whichever one the source returned first.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Errors","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Error","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cause","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fix","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"422 Either routers or pageProcessors should be present in the flow configuration and not both.","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Top-level ","type":"text"},{"text":"pageProcessors[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" sent next to ","type":"text"},{"text":"routers[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- often a linear flow turned into a branching one that kept its old steps","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Move the top-level steps into the branches (or into a pass-through router before the branch point) and leave top-level ","type":"text"},{"text":"pageProcessors","type":"text","marks":[{"type":"code_inline"}]},{"text":" out","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"422 Either both the routeRecordsTo and routeRecordsUsing fields or none of them should be defined on the router.","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A router with only one of the two routing fields","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Set both on a branching router; set neither on a one-branch pass-through router","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"422 Field operation extract must be called by a Type Conversion Operation but was called by equals.","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A bare ","type":"text"},{"text":"[\"extract\", \"\u003cfield>\"]","type":"text","marks":[{"type":"code_inline"}]},{"text":" in a branch's ","type":"text"},{"text":"rules","type":"text","marks":[{"type":"code_inline"}]},{"text":"; the message names the operator around it","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wrap it: ","type":"text"},{"text":"[\"string\", [\"extract\", \"\u003cfield>\"]]","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or ","type":"text"},{"text":"number","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"boolean","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"epochtime","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Every record goes down one branch, and its ","type":"text"},{"text":"inputFilter","type":"text","marks":[{"type":"code_inline"}]},{"text":" is missing on read-back","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The filter was sent in the export/import wrapper, ","type":"text"},{"text":"{ type: \"expression\", expression: {...} }","type":"text","marks":[{"type":"code_inline"}]},{"text":", which saves without an error and is stored as no filter","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Send ","type":"text"},{"text":"{ version: \"1\", rules: [...] }","type":"text","marks":[{"type":"code_inline"}]},{"text":" on the branch, then read the flow back","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A flow-level schedule change answered 200, but the flow still runs at the old time","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A page generator has its own ","type":"text"},{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]},{"text":", which overrides the flow's","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Change that generator's ","type":"text"},{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]},{"text":" with a full update of the flow","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"422 abstract_flow_invalid_fields","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Top-level ","type":"text"},{"text":"pageProcessors","type":"text","marks":[{"type":"code_inline"}]},{"text":" on an abstract flow (","type":"text"},{"text":"isAbstract: true","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Move the chain into one pass-through router in ","type":"text"},{"text":"routers[]","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"422 invalid_flow","type":"text","marks":[{"type":"code_inline"}]},{"text":" (\"... already included as a pageProcessor\")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The same import is a step twice in one flow, in one list or in two branches","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rejoin the branches through one pass-through router that holds the shared step (","type":"text"},{"text":"Branching Flows","type":"text","marks":[{"type":"link","attrs":{"href":"#branching-flows","title":null}}]},{"text":")","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"422 invalid_flow_router_branch","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A router with neither ","type":"text"},{"text":"routeRecordsTo","type":"text","marks":[{"type":"code_inline"}]},{"text":" nor ","type":"text"},{"text":"routeRecordsUsing","type":"text","marks":[{"type":"code_inline"}]},{"text":" (a pass-through) has more than one branch","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Give a pass-through router exactly one branch, or set both fields to make it a branching router","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A record's error ","type":"text"},{"text":"invalid_script_routing","type":"text","marks":[{"type":"code_inline"}]},{"text":" (stage ","type":"text"},{"text":"routing","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A branching script returned more than one index under ","type":"text"},{"text":"first_matching_branch","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Return one index per record, or switch the router to ","type":"text"},{"text":"all_matching_branches","type":"text","marks":[{"type":"code_inline"}]},{"text":" if the record belongs in several branches","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"400 invalid_ref","type":"text","marks":[{"type":"code_inline"}]},{"text":" (\"Please provide a valid _integrationId.\") / ","type":"text"},{"text":"422 invalid_ref","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"_integrationId","type":"text","marks":[{"type":"code_inline"}]},{"text":" isn't a well-formed id (400), or names no integration in the account (422)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Read the integration by id to confirm it exists","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Invalid reference: _exportId","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"_importId","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Export or import referenced in a page generator/processor does not exist","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Create the export/import first, then reference it","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Invalid reference: _connectionId","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Connection ID on an export or import does not exist","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Read the connection by id to confirm it exists","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Duplicate router id","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Two routers in the same flow share the same ","type":"text"},{"text":"id","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Assign unique alphanumeric IDs to each router","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Invalid nextRouterId","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A branch references a router ","type":"text"},{"text":"id","type":"text","marks":[{"type":"code_inline"}]},{"text":" that does not exist in the flow","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Ensure ","type":"text"},{"text":"nextRouterId","type":"text","marks":[{"type":"code_inline"}]},{"text":" matches an actual router ","type":"text"},{"text":"id","type":"text","marks":[{"type":"code_inline"}]},{"text":" in the same flow","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"422 schedule_not_supported","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]},{"text":" is free text (","type":"text"},{"text":"every 5 minutes","type":"text","marks":[{"type":"code_inline"}]},{"text":") or would run less than a minute apart (","type":"text"},{"text":"* * * * * *","type":"text","marks":[{"type":"code_inline"}]},{"text":") -- refused on every write, a disabled flow's included","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use the 6-field format: ","type":"text"},{"text":"\"? */5 * * * *\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" (seconds field first, always ","type":"text"},{"text":"?","type":"text","marks":[{"type":"code_inline"}]},{"text":"), or clear the schedule","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"422 invalid_value","type":"text","marks":[{"type":"code_inline"}]},{"text":" on ","type":"text"},{"text":"schedule","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The cron parses to no schedule, such as all wildcards (","type":"text"},{"text":"? * * ? * *","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Name the minute and hour: ","type":"text"},{"text":"\"? 5 0 ? * *\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" runs daily at 00:05","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"422 dependencies_not_deleted","type":"text","marks":[{"type":"code_inline"}]},{"text":" (\"flow ... must be deleted before you can delete flow ...\") on a flow delete","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Another flow lists this one in ","type":"text"},{"text":"_runNextFlowIds","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Remove it from that flow's ","type":"text"},{"text":"_runNextFlowIds","type":"text","marks":[{"type":"code_inline"}]},{"text":", then delete","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Flow runs immediately after creation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Created with ","type":"text"},{"text":"disabled: false","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"disabled","type":"text","marks":[{"type":"code_inline"}]},{"text":" omitted (defaults to enabled)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Always set ","type":"text"},{"text":"disabled: true","type":"text","marks":[{"type":"code_inline"}]},{"text":" on create; enable after verification","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"400 invalid_connectionMap","type":"text","marks":[{"type":"code_inline"}]},{"text":" on a clone","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The flow's steps use connections, and the map is missing or empty","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Map every connection the flow's steps use -- to itself to reuse it, or to an existing connection in the target environment","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"An edit to an Integration App flow \"succeeds\" but nothing changed","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The app locks that field: the full update answered 200 and kept the app's value","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Change it through the app's settings, or build a companion flow outside the app","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"409 job_already_queued","type":"text","marks":[{"type":"code_inline"}]},{"text":" on a run","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"The flow is already queued or running -- one instance at a time","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wait for the in-progress run to finish, then run again. A run-next trigger that arrived meanwhile was dropped, not queued","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Destination records created twice","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"A repeat reached a write that isn't idempotent","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Read the runs in the window first (","type":"text"},{"text":"Duplicate Records","type":"text","marks":[{"type":"link","attrs":{"href":"#duplicate-records","title":null}}]},{"text":"), then key and lock the write (","type":"text"},{"text":"configuring-imports > Writing Exactly Once","type":"text","marks":[{"type":"link","attrs":{"href":"../configuring-imports/SKILL.md#writing-exactly-once","title":null}}]},{"text":")","type":"text"}]}]}]}]}]},"metadata":{"date":"2026-11-02","name":"building-flows","author":"@skillopedia","source":{"stars":4,"repo_name":"ai","origin_url":"https://github.com/celigo/ai/blob/HEAD/skills/building-flows/SKILL.md","repo_owner":"celigo","body_sha256":"df1a2b649a0d98fffd9eac1ca9df22043fbd214d05bd1c28ab28dc193dd975ea","cluster_key":"99e8080bef8157208c359efeb2b79a82dcc43082b2f0ea289f3f69731c94f8b0","clean_bundle":{"format":"clean-skill-bundle-v1","source":"celigo/ai/skills/building-flows/SKILL.md","text_bytes":124516,"source_commit":"2f3853ed93eece259b8c78ee74ef19035dfae052","skills_sh_rank":6239,"attachment_count":16,"text_attachments":16,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"skills/building-flows/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"integrations-apis","category_label":"Integrations"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"integrations-apis","import_tag":"skills-sh-top20k-2026-11","description":"Build Celigo flows -- pipelines that move data from source systems to destination systems on a schedule or in response to events. Covers scheduling, chaining, error management, and abstract/instance templating. Use when creating, editing or explaining flows -- what each one does, when it runs and where its records go; to diagnose a flow that fails, errors, brings no data, or runs slowly, use troubleshooting-flows."}},"renderedAt":1793661502310}

<!-- TIER:1 -- Building Flows A flow moves data from one or more source systems to one or more destination systems. It runs on a schedule, in response to events (webhooks, listeners), or when triggered by another flow. Flows are the primary way integrations get work done in Celigo. A flow has page generators (exports that fetch data) and page processors (imports and lookups that process each record). Processors run sequentially in a flat list, or conditionally through routers that branch records to different paths. These processing pipeline mechanics -- routers, branches, page processors, res…