360 Secure SkillHub
360 安全技能中心

building-flows

该 Skill 指导使用 Celigo CLI 创建、修改和调试 Flow 数据集成管道,涵盖 page generator/processor、线性或分支路由、调度、响应映射、错误重试、重复数据处理以及抽象/实例模板化部署。

celigo
热度 38.22
最近更新 2026-10-19 03:34:32
分类:自动化依赖:代码执行 / 读写文件 / 第三方 API应用场景:工作流 / API 集成 / 数据处理 / 日程安排 ...

安装技能

复制下面的话术发给你的 Agent,它会通过 secure-skills 检索并安装该技能。

执行命令安装此 skill: npx secure-skills-cli add celigo/ai/building-flows
Metadata
name
building-flows
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 debugging flows.

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).

What Starts a Flow

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:

  • Scheduled exports -- 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.
  • Listeners -- 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.

A flow can mix both, and multi-generator designs are common:

  • Real-time plus batch safety net -- a listener catches events as they fire; a scheduled export reconciles at off-peak hours, catching up after webhook outages
  • Consolidating sources -- customers from Salesforce AND HubSpot, each with its own generator, feeding the same downstream pipeline
  • Different slices of the same source -- one export pulls new records, another pulls updated records, when the API exposes them separately

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.

Fetched Data Needs a Downstream Consumer

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 write-back import to the source system (most common) -- e.g. source export -> preview import -> update import that writes the captured fields onto the source record
  • A persistent destination the user named (file to S3/SFTP, email, database)
  • A router or AI agent step that consumes the captured data within the same run

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.

Flow Topologies

Linear Flows

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.

Branching Flows

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):

  • Input filters (routeRecordsUsing: "input_filters") -- S-expression rules on each branch; last branch can omit filter as a catch-all
  • Script-based (routeRecordsUsing: "script") -- a JavaScript branching function returns, per record, an array of branch indices -- zero-based positions in branches[] (e.g. [0])

Flows 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. Leaving both unset is the pass-through shape; the server fills in no default.

Abstract / Instance Flows

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.

Quick Reference

Flow Type Decision Matrix

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

Minimum Required Fields

Every flow needs at minimum:

  • name -- display name
  • _integrationId -- parent integration
  • disabled: true -- always create disabled
  • pageGenerators[] -- at least one entry with _exportId
  • Either pageProcessors[] (linear) or routers[] (branching) -- never both

Which Schemas to Read

Always read:

Add for branching flows:

  • router.yml -- routing strategy, record distribution mode
  • branch.yml -- input filters, per-branch processors, chaining

Add if response mapping is needed:

All available schemas (in references/schemas/):

Related Skills

How to Build a Flow

1. Plan the flow

Before creating anything, decide what kind of operation this is:

Decision tree:

  • Modifying an existing flow's step config (export settings, import mappings, scripts) -- work on the step directly, not the flow. Use celigo exports set, celigo imports set, or the relevant skill (configuring-exports, configuring-imports, writing-scripts, writing-mappings)
  • Modifying an existing flow's structure (add/remove steps, change schedule, rename) -- GET the flow, modify the structure, PUT it back. Don't rebuild from scratch
  • Building a new flow where every step is known -- build directly, bottom-up (skip to step 2)
  • Any ambiguity about what steps are needed -- design first. List every system, every data direction, every step before writing any JSON

Design checklist (when ambiguity exists):

  • What source systems? What destination systems?
  • What data moves between them, in which direction?
  • How often? (cron schedule, webhook trigger, on-demand)
  • What happens when a step fails? (proceedOnFailure, error notifications)
  • Do downstream steps need data from upstream responses? (response mapping)
  • Is this a one-off or a reusable template? (abstract/instance flow)
  • Sandbox or production? (never mix -- sandbox: true flows only use sandbox: true connections)

2. Identify the integration

Every flow belongs to an integration (the container). Find or create the integration first.

celigo integrations list

3. Check for existing patterns

Before building from scratch, check what already exists in the account and marketplace.

# Search for similar resources in the account index
celigo account search "<keyword>"

# Show what an existing resource uses and what uses it
celigo account dependencies flow <id>

# Find orphaned resources, offline connections, untriggered flows
celigo account lint

# Search marketplace for pre-built integration templates
celigo templates marketplace

# Preview a template before installing
celigo templates preview <id> --summary

The account index auto-refreshes when stale (older than 15 minutes by default). Force a fresh snapshot with celigo account snapshot.

4. Build the connections, exports, and imports

Flows reference existing resources. Build bottom-up: connections first, then exports and imports that use those connections, then the flow that wires them together.

celigo connections list
celigo exports list
celigo imports list

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.

5. Choose the topology

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. An abstract flow can't use top-level pageProcessors -- the platform rejects them with 422 abstract_flow_invalid_fields -- so put even a linear chain in routers[], as one pass-through router (a single branch, neither routeRecordsTo nor routeRecordsUsing set). 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.

6. Design the step sequence

For each step, decide:

  • Type -- import (write to destination) or export (lookup for enrichment)
  • Response mapping -- 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 postResponseMap 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).
  • proceedOnFailure -- should the pipeline continue if this step fails?
  • Hooks -- does this step need a postResponseMap script?

7. Build the flow JSON

Reference the schemas listed in the Quick Reference above for exact field schemas.

8. Configure scheduling

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).

9. Set the runtime controls

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 per page generator (the export has its own, shown as "Do not store retry data") false (failed records keep their retry data and can be retried) 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 (configuring-imports > Writing Exactly Once)
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 true 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 intermittent retry automatically (up to four attempts, the first about 30 minutes later) 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 duplicate classification means the destination already holds the record, so turning this off doesn't fix it

10. Configure chaining (if needed)

  • _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 flow
  • Chaining passes no records. 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 configuring-lookup-caches > Deduplication Ledger
  • A flow runs one instance at a time. A run requested while it is queued or running is refused with 409 job_already_queued, and a run-next trigger that arrives then is dropped, not queued -- several parent completions in quick succession start the child once

11. Create disabled, test, enable

Always create with disabled: true. Verify the structure with celigo flows get, then test-run it before you enable it:

celigo flows test-run <id> --export <exportId>                       # one page from that generator
celigo flows test-run-step-results <id> <runId> <exportOrImportId>  # per-stage input, output, and errors

The flow may be enabled or disabled -- an enabled flow's test run answers 200 and runs (live-verified 2026-10-14; CLI 2026.9.x's help still says it must be disabled). A test run is not a dry run of everything:

  • Imports never send. Each returns its saved mockResponse, or the placeholder {"id": "1234567890", "mockResponse": "replace me"} -- so response mappings after an import carry nothing real until you save a realistic mock (configuring-imports > Testing an Import Without Writing).
  • Exports and lookups read the live source, unless the export has a saved mockOutput, which is replayed instead (the export's transform and filter still run on it).
  • Hooks run, and nothing sandboxes the integrator-api module in test mode: treat a hook's writes -- to a lookup cache or state, or an import or flow it runs -- as real.

Enable only after the test run looks right.

Duplicate Records

Every source delivers some records more than once, and nothing between the source and the write removes the repeats:

Where the repeat comes from Why
The sender fires again (a webhook retry, a button clicked twice) A listener never de-duplicates; every request is its own record
A failed or canceled run is re-run The delta checkpoint advances only at the end of a successful run, so the next run re-reads the same window
A partial failure 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
Error retries intermittent errors retry automatically, and manual retries re-send the captured record
Another flow writes the same destination Checks inside one flow can't see the other

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 -- configuring-imports > Writing Exactly Once.

Diagnosing "it created duplicates" -- read runs before configuration. One clean latest run proves nothing.

  1. List the runs around the time the user names -- celigo jobs list --type flow --flow <id> --created-gte <iso> --created-lte <iso> -- and read each with celigo jobs get <jobId>, which inlines the per-step child jobs and their numSuccess / numError / numIgnore.
  2. 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.
  3. Then the configuration: is the write keyed (a destination-enforced key, or ignoreExisting + an existence check) or a bare create? Does idLockTemplate -- or the default trace key -- render non-empty for a real record (celigo flows error <flowId> <importId> <errorId> --retry-data), not a sample? What marks a record done, and does a failure skip that step?

CLI Commands

# 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-time <ISO8601>] [--end-time <ISO8601>] [--export-ids <ids>] -y

# Test run (stage-by-stage; the flow may be enabled or disabled)
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> < data.json
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...>

Pre-Submit Checklist

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 immediately
  • schedule 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
  • Router IDs are unique within the flow (random alphanumeric, e.g., "N8Q9NX24Sj5")
  • Every branch nextRouterId references an existing router id in the same flow

Gotchas

  1. PUT erases omitted fields. Always GET first, modify, then PUT. The set command handles this automatically.
  2. pageProcessors and routers are mutually exclusive. A flow uses one or the other at the top level. Setting both causes validation errors.
  3. Router IDs must be unique within a flow. Use random alphanumeric strings (e.g., "N8Q9NX24Sj5"). nextRouterId must reference an existing router id in the same flow.
  4. Create flows with disabled: true. An enabled flow with a schedule will run immediately. Enable only after verification.
  5. Build order matters. Connection -> Export -> Import -> Flow. The API rejects references to non-existent resources.
  6. Schedule is 6-field cron with seconds. Format: "? minute hour dayOfMonth month dayOfWeek". The first field is always ?. Common mistake: using 5-field cron without the seconds position.
  7. Instance flows cannot define structure. Do not set pageGenerators, pageProcessors, or routers on instance flows -- these are inherited from the abstract flow. All customizations go through overrides.
  8. Instance flow overrides is full-replace on PUT. Omitting an override entry removes it. Always GET, merge changes, then PUT.
  9. Empty pageProcessors: [] in a branch is the discard pattern. Records matching that branch are dropped. A branch with no inputFilter serves as a catch-all.
  10. responseMapping uses Transformation 1.0 syntax (extract/generate pairs), not expression-based transforms. Lookup export responses use data[0].fieldName; import responses use id for the created record's ID and _json.fieldName for response-body fields.
  11. Don't add identity transforms on lookups. A lookup's transform only reshapes its results (the data 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 (data[0].<field>). Only add a transform to rename fields, reshape nested structures, or drop fields; an identity transform (e.g., errorId -> $.errorId) adds complexity for no benefit.
  12. Trace keys never de-duplicate. 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 Duplicate Records.
  13. A test run isn't side-effect free. Imports never send, but exports and lookups read live systems and hooks execute -- treat a hook's integrator-api writes as real. Before test-running against production systems, save a mockResponse on imports and a mockOutput on any lookup you don't want called.

Common Errors

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
422 abstract_flow_invalid_fields Top-level pageProcessors on an abstract flow (isAbstract: true) Move the chain into one pass-through router in routers[]
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
409 job_already_queued on flows run The flow is already queued or running -- one instance at a time Wait for it (celigo jobs current --flow <id>), then run again. A run-next trigger that arrived meanwhile was dropped, not queued
Destination records created twice A repeat reached a write that isn't idempotent Read the runs in the window first (Duplicate Records), then key and lock the write (configuring-imports > Writing Exactly Once)