npx skills add ...
npx skills add celigo/ai --skill building-flows
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 debugging flows.
npx skills add celigo/ai --skill 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, response mapping -- are shared with APIs and tools (see building-apis and building-tools).
Flows start themselves -- 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:
A flow can mix both, and multi-generator designs are common:
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.
A common design mistake: ending a flow on an import that fetches 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.
When the requirement says "preview / estimate / retrieve / fetch / check / look up", the design needs at least one of:
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.
A flat pageProcessors[] 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: "import") or a lookup export (type: "export"). Records pass through every step in order. Unique to flows -- APIs and tools always use routers.
Page generators feed records into routers[] instead of pageProcessors[]. Each router evaluates records against branch conditions and routes them to matching branches. Branches contain their own pageProcessors[] and can chain to other routers via nextRouterId.
Two routing modes (shared with APIs and tools):
routeRecordsUsing: "input_filters") -- S-expression rules on each branch; last branch can omit filter as a catch-allrouteRecordsUsing: "script") -- a JavaScript function returns the branch nameFlows support both first_matching_branch and all_matching_branches routing. APIs only support first_matching_branch. Tools support first_matching_branch only.
A flow uses EITHER pageProcessors (linear) OR routers (branching) at the top level -- not both.
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 pass-through router: a single-branch router with nextRouterId pointing to the branching router. Omit routeRecordsTo and routeRecordsUsing on the pass-through router -- including them makes it appear as a filter-based branch in the UI. The API defaults are sufficient.
A template/inheritance model. An abstract flow (isAbstract: true) defines the complete graph but cannot execute. Instance flows (_abstractFlowId) inherit the graph and customize via an overrides object (connections, schedules, mappings, filters).
Use when the same flow structure is deployed across multiple regions, tenants, or environments with different connections or parameters.
| Pattern | Structure | Key fields | Read schema |
|---|---|---|---|
| Linear | Flat processor list | pageGenerators[], pageProcessors[] | request.yml, page-generator.yml, page-processor.yml |
| Branching (routers) | Routers with conditional branches | pageGenerators[], routers[] | + router.yml, branch.yml |
| Abstract / Instance | Template + per-instance overrides | isAbstract: true / _abstractFlowId, overrides | + overrides-helper.yml, overrides.yml |
Every flow needs at minimum:
name -- display name_integrationId -- parent integrationdisabled: true -- always create disabledpageGenerators[] -- at least one entry with _exportIdpageProcessors[] (linear) or routers[] (branching) -- never bothAlways read:
Add for branching flows:
Add if response mapping is needed:
All available schemas (in references/schemas/):
Before creating anything, decide what kind of operation this is:
Decision tree:
celigo exports set, celigo imports set, or the relevant skill (configuring-exports, configuring-imports, writing-scripts, writing-mappings)Design checklist (when ambiguity exists):
proceedOnFailure, error notifications)sandbox: true flows only use sandbox: true connections)Every flow belongs to an integration (the container). Find or create the integration first.
Before building from scratch, check what already exists in the account and marketplace.
The account index auto-refreshes when stale (>4 hours). Force a fresh snapshot with celigo account snapshot.
Flows reference existing resources. Build bottom-up: connections first, then exports and imports that use those connections, then the flow that wires them together.
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 for a pre-built HTTP connector (550+ apps: celigo http-connectors list) 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 configuring-exports > Check for a pre-built connector and configuring-imports > Check for a pre-built connector.
See configuring-exports and configuring-imports for how to build each resource.
| Scenario | Topology |
|---|---|
| All records follow the same path | Linear (pageProcessors) |
| Records need conditional routing by field values | Branching with input filters |
| Routing logic requires custom JavaScript | Branching with script router |
| Records should fan out to all matching paths | Branching with all_matching_branches |
| Same structure across multiple tenants/regions | Abstract + instance flows |
Abstract/instance flows: 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 isAbstract: true. Top-level pageProcessors are automatically wrapped into a single-branch router. Instance flows reference the abstract via _abstractFlowId and specify overrides -- they do NOT use the normal scaffolding process. _integrationId is NOT inherited and must be set explicitly on the instance.
For each step, decide:
import (write to destination) or export (lookup for enrichment)postResponseMap script?Reference the schemas listed in the Quick Reference above for exact field schemas.
Pair schedule (6-field cron) with timezone (IANA). Omit both for listener/webhook/realtime flows. timezone defaults to UTC, which is usually wrong for human-facing schedules ("9 AM every weekday" should survive daylight saving).
Individual page generators can override the flow schedule via their own schedule field (e.g. one source syncs hourly, another nightly, in the same flow).
Flow-level and per-step switches that change production behavior. APIs and tools have none of these -- they are flow-only.
| Control | Where | Default | Flip it when |
|---|---|---|---|
proceedOnFailure | per processor | false (a failed record stops there) | 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 false when downstream depends on this step's output |
skipRetries | flow, and per generator | false (failed jobs retry) | Work is time-sensitive (retrying a stale webhook is meaningless) or non-idempotent (retries risk duplicates). Per-generator override: set it only on the real-time generator |
runPageGeneratorsInParallel | flow | false (generators run sequentially) | Sources are independent and can take the load. Careful: parallel generators hitting the same API can blow rate limits that sequential runs respect |
autoResolveMatchingTraceKeys | flow | duplicate trace keys raise an error | The source genuinely emits duplicates in normal operation, or the flow is intentionally idempotent. Don't enable it to paper over upstream duplication |
_runNextFlowIds -- 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, then Y", chain two focused flows rather than building one big one_runNextExportIds -- more granular: trigger specific exports inside other flows instead of the whole flowAlways create with disabled: true. Verify the structure with celigo flows get. Enable only after verification.
Before creating or updating a flow, verify:
_integrationId references a real integration (confirm with celigo integrations get <id>)disabled: true is set for initial creation -- an enabled flow with a schedule runs immediatelyschedule is 6-field cron with seconds: "? */5 * * * *" (first field is always ?)pageProcessors[] and routers[] are mutually exclusive -- a flow uses one or the other, never both"N8Q9NX24Sj5")nextRouterId references an existing router id in the same flowset command handles this automatically.pageProcessors and routers are mutually exclusive. A flow uses one or the other at the top level. Setting both causes validation errors."N8Q9NX24Sj5"). nextRouterId must reference an existing router id in the same flow.disabled: true. An enabled flow with a schedule will run immediately. Enable only after verification."? minute hour dayOfMonth month dayOfWeek". The first field is always ?. Common mistake: using 5-field cron without the seconds position.pageGenerators, pageProcessors, or routers on instance flows -- these are inherited from the abstract flow. All customizations go through overrides.overrides is full-replace on PUT. Omitting an override entry removes it. Always GET, merge changes, then PUT.pageProcessors: [] in a branch is the discard pattern. Records matching that branch are dropped. A branch with no inputFilter serves as a catch-all.responseMapping uses Transformation 1.0 syntax (extract/generate pairs), not expression-based transforms. Lookup export responses use data[0].fieldName; import responses use _json.fieldName.errorId -> $.errorId) adds complexity for no benefit.| Error | Cause | Fix |
|---|---|---|
"pageProcessors" is not allowed when "routers" is present | Both pageProcessors[] and routers[] set on the same flow | Remove one -- use pageProcessors for linear, routers for branching |
Invalid reference: _integrationId | Integration ID does not exist or is misspelled | Verify with celigo integrations get <id> |
Invalid reference: _exportId / _importId | Export or import referenced in a page generator/processor does not exist | Create the export/import first, then reference it |
Invalid reference: _connectionId | Connection ID on an export or import does not exist | Verify with celigo connections get <id> |
Duplicate router id | Two routers in the same flow share the same id | Assign unique alphanumeric IDs to each router |
Invalid nextRouterId | A branch references a router id that does not exist in the flow | Ensure nextRouterId matches an actual router id in the same flow |
Invalid cron expression | Schedule uses 5-field cron or wrong format | Use 6-field format: "? */5 * * * *" (seconds field first, always ?) |
| Flow runs immediately after creation | Created with disabled: false or disabled omitted (defaults to enabled) | Always set disabled: true on create; enable after verification |
celigo connections list
celigo exports list
celigo imports list# CRUD
celigo flows list
celigo flows get <id>
celigo flows create < flow.json
celigo flows update <id> < flow.json
celigo flows set <id> key=value [key2=value2 ...]
celigo flows delete <id>
# Run
celigo flows run <id> [--start-date <ISO8601>] [--end-date <ISO8601>] [--export-ids <ids>] -y
# Test run (stage-by-stage)
celigo flows test-run <id> --export <exportId>
celigo flows test-run-step-results <id> <runId> <exportOrImportId>
# Clone
echo '{"connectionMap":{"oldId":"newId"}}' | celigo flows clone <id> <integrationId> <environmentId> [--flow-group <id>]
# Structure manipulation
celigo flows add-generator <id> <exportId> [--schedule '<cron>'] [--index <pos>]
celigo flows remove-generator <id> <exportId>
celigo flows add-processor <id> <exportOrImportId> [--router <routerId>] [--branch <branchName>]
celigo flows remove-processor <id> <exportOrImportId> [--router <routerId>] [--branch <branchName>]
celigo flows replace-connection <id> <oldConnectionId> <newConnectionId>
# Error management
celigo flows errors <id> <exportOrImportId>
celigo flows resolved-errors <id> <exportOrImportId>
celigo flows resolve-errors <id> <exportOrImportId> [errorIds] [-y]
celigo flows retry-errors <id> <exportOrImportId> [retryDataKeys] [-y]
celigo flows assign-errors <id> <exportOrImportId> <email> [errorIds] [-y]
celigo flows delete-resolved-errors <id> <exportOrImportId> [errorIds] [-y]
celigo flows error <id> <exportOrImportId> <errorId> [--retry-data] [--request-detail]
celigo flows update-error-data <id> <exportOrImportId> <errorId>
celigo flows tag-errors <id> <exportOrImportId>
celigo flows error-summary <id>
celigo flows error-analysis <id> <exportOrImportId> [--limit <n>]
# Debug
celigo flows debug-requests <id> <exportOrImportId> [--since <minutes>]
celigo flows debug-request-detail <id> <exportOrImportId> <key>
celigo flows enable-execution-logs <id> [--duration <minutes>]
celigo flows disable-execution-logs <id>
celigo flows execution-logs <id> <jobId>
celigo flows query-execution-logs <id> <jobId> --export-or-import-id <id> --group-id <gid> --record-id <rid>
celigo flows execution-log-detail <id> <jobId> --export-or-import-id <id> --stage <stage> --group-id <gid> --record-id <rid
# Metadata
celigo flows last-export-date <id>
# Integration-level flow management
celigo integrations flow-groups <integrationId>
celigo integrations create-flow-group <integrationId> <name>
celigo flows set-group <flowGroupingId> <flowIds...>