Promote to another account
celigo promote --to <profile> writes a local tree into another account or environment: it creates what the target lacks and updates what it holds, with every reference remapped to the target's ids. Sandbox to production inside one account and account to account are the same mechanism — each environment's API token is its own principal, and the two never see each other's ids.
The source is the tree, bound to the account it was pulled from. Promote never rebinds it, never writes its files or its manifest, and never takes --profile; the only account it touches is the one --to names. State per target lives in a committed .celigo/targets/<name>.json, so the second promote is an update rather than a second copy, and a teammate or a CI job at the same commit promotes to the same target ids.
Required: full mode on the target profile. Both sides are announced on stderr before the first write: celigo: promote tree of profile 'prod' (api.integrator.io, account user 624c…) → profile 'sandbox' → api.integrator.io.
Signature
celigo promote --to <profile|target> [refs...] [flags]Arguments
[refs...]
file path, type:id, or bare id
No
Narrow the promote to these resources, as for push.
Flags
--to <profile|target>
string
—
Required. The target: a profile name, or the name of an existing target file under .celigo/targets/.
--dir <path>
string
.
Tree root directory.
--dry-run
boolean
false
Compute and print the plan without writing anything. Exits 1 when any item is refused.
--integration <id|path>
string
—
Narrow to one integration: its document, everything carrying its _integrationId, and the closure of what those reference in the tree.
--map <type/source=target-id>
string, repeatable
—
Pin a per-environment resource to a target id, for example connection/connections/Shopify/connection.json=<target id> (a source path or a source id). Persisted in the target file, so it is given once.
--create-connections
boolean
false
Create a credential-less shell in the target for a connection, iClient, stack, or on-premise agent that has no namesake and no pin.
--adopt-by-name
boolean
false
Map a same-named target resource in the same place to the source file and update it, instead of refusing to create a second one.
--prune
boolean
false
Delete the target copies of resources removed from this tree. Asks for confirmation.
--force-remote
boolean
false
Overwrite (or delete) target copies that changed since the last promote.
--allow-account-mismatch
boolean
false
Promote into a different account than the target file is bound to (host or token owner), rebinding it.
--allow-duplicate-names
boolean
false
Skip the namesake listings in the target: the plan-time pre-flight and the re-check before each type's first POST.
-y, --yes
boolean
false
Skip the delete confirmation prompt (--prune).
--concurrency <n>
integer
8
Requests in flight at once (1–16). CELIGO_CONCURRENCY sets the default. Writes still run in dependency order, deletes dependents-first.
--force-lock
boolean
false
Take the tree lock even if another pull, push, or promote appears to hold it.
Example — the first promote of an integration into a sandbox environment, then the repeat:
The target file
.celigo/targets/<name>.json is committed beside the manifest (pull's .celigo/.gitignore already leaves targets/ tracked) and validated on load with the manifest's rigor. It records:
every source
type:id→ the target copy's id, with the target'slastModifiedlock token — or, for a document the platform returns without one (a sync), the hash of the copy the last write left there — and the hash of the body last sent;how each connection, iClient, stack, and on-premise agent resolved (
name,pin,adopted,shell);the flow-group id map;
the create-intent markers of an interrupted run;
the target's identity: host plus the token owner's user id.
It holds ids, hashes, and timestamps — never an API token.
--to resolves to an existing target file first, then to a profile; the first promote creates the file under the profile's name. The recorded profile name is a label: a machine with no profile named production still promotes to the committed production target, provided it has the target's token (below). A merely renamed profile needs no override — edit the profile label in the file.
The target's token
The target is reached with the --to profile's stored token, or — on a CI runner with no such profile — with CELIGO_TARGET_API_TOKEN and CELIGO_TARGET_BASE_URL (default https://api.integrator.io). A note says when the variable stands in, and the variable wins over a stored token when both exist.
CELIGO_API_TOKEN, CELIGO_BASE_URL, --token, and --base-url belong to the source session and are never applied to the target. A target with neither a stored token nor the variable is refused, naming both exits, instead of promoting the tree back into the source account under the target profile's name. --to naming the tree's own account is refused too: use celigo push.
The target's identity
Promote verifies the target's account before anything is read — one GET /v1/tokenInfo, never retried — and is stricter about it than push. A target file bound to another account is refused unless --allow-account-mismatch rebinds it, which starts its map, bindings, and group ids over (they name the old account's resources), so the next promote creates everything again there and says so.
When the probe fails (a known platform issue makes GET /v1/tokenInfo return 500 Internal Server Error for some production tokens), push degrades to the host match with a warning. Promote does the same only for a target not yet bound to an account user — the first promote. A bound target is refused, because a promote writes into another environment under a second token and the host alone cannot tell that token from a colleague's on the same host. Retry once the probe answers, or pass --allow-account-mismatch to accept the host match for that run; the binding is kept as recorded.
The plan
Plan first, in push's vocabulary. create, update, and delete are sent. unchanged (the body last sent has not changed — no request) and up to date (the target already holds exactly this body — its token is adopted, no request) cost nothing. A map: line per per-environment resource says how it resolved: map: connection 'Shopify' → 64f… (matched by name | pinned with --map | shell to be created). refused: <path> — <reason> and blocked: <path> — <pointer> names <path>, whose promote is refused fail the run — exit 1, from --dry-run too. withheld: (another integration's resources; an Integration App's documents — install the app in the target instead) is not sent and not a failure. note: lines on stderr carry orphaned target copies, dropped group ids, literal-id splices in scripts, and registrations.
Every plan-time guard push applies: a document whose _id is not the tracked resource's, masked credentials, the responseMapping object wrapper or fields: null, an unresolved reference — a placeholder, an @bindings/ key, or an id naming nothing in the tree, and a tracked resource left out by refs or --integration, which the refusal says. A refused create blocks its dependents; a refused update does not, since its target copy is there to point at. Promote works from the manifest: a resource file the tree does not track (a draft never pushed to the source) is not promoted, and a note says to push it first.
References are rewritten through the map: declared references, ids embedded in URIs, and — disclosed one splice at a time — literal ids in script source and settings whose target copy is known. Writes run in dependency order, then deletes dependents-first; a reference cycle is created without the deferred half and repaired by a disclosed fixup PUT. The legacy sandbox flag never travels. Flow groups travel by name: an integration's flowGroupings lose their ids on create and keep the target's on update, and a flow's _flowGroupingId is remapped, or dropped with a note until the group exists there. An integration's registered connections and lookup caches are registered after it lands.
Repeat semantics. A source whose last-sent body is unchanged sends nothing: a no-op promote of a whole tree is the identity probe plus one listing per per-environment type. A changed source costs one GET (the lock) and one PUT. A target copy whose lastModified moved since the last promote — someone edited it in the target's UI — is refused until you look: celigo diff --target <name> <path> to inspect, then --force-remote to overwrite, or pull the target and carry the change back into the source. A target copy that no longer exists is created again with a note, and the documents that reference it are re-pointed in the same run; because an unchanged source costs no request, a copy deleted in the target is noticed once its source changes, while celigo diff --target shows it as deleted in target at any time.
Connections and the other per-environment resources
Connections, iClients, stacks, and on-premise agents are never promoted with credentials: the tree holds ****** placeholders, and a copy without its secret is useless anyway. Each one the promoted documents reference resolves in the target in this order:
An explicit pin —
--map connection/<source path or id>=<target id>— persisted in the target file.The persisted binding from an earlier promote, if the target still has it.
Exactly one namesake of the same name and adaptor
typein the target.
None → the resource is refused and every dependent (its exports, imports, the integration that registers it, the flows) is blocked, with both exits in the message: the pin, or --create-connections. Several namesakes → refused; a pin decides.
--create-connections creates a credential-less shell in the target — the same name, type, and every non-secret field, with every masked ****** value replaced by the placeholder REPLACE-IN-UI (the platform rejects some connections when the field is missing outright) and every plaintext secret-named field omitted — and binds the dependents to it. Enter the real credentials once, either way the note: names: in the target's UI, or with the CLI:
connections set patches only name and the debug window, so it cannot reach a credential; update is the full-document PUT. Promote never writes the connection again after the shell — the binding is an id — so what you enter there stays.
The recommended discipline is the same as for neutral trees: name connections the same in every environment, and the first promote maps them all by name with no flags.
The first promote into an existing target
A target that already holds hand-built copies — the usual state of a production account that predates the tree — is where names bite. Promote's namesake pre-flight finds a same-name, same-type resource in the same place (the mapped integration, or account level) that is not in the map, and refuses to create a second one beside it. The refusal names --adopt-by-name, which maps that resource to the source file and updates it in place from then on. "Same type" includes the adaptor: an HTTP export is never adopted for a NetSuite export of the same name, nor refused on its account.
Run the first promote as a dry run, read the refusals, decide per resource — adopt, or rename one of the two — then promote with the flag. The pre-flight runs again right before each type's first POST, against a listing fetched in that run, so a namesake that appeared between the plan and the write (a colleague promoting the same commit, the UI) fails that create rather than producing a second copy. --allow-duplicate-names skips both listings.
Prune deletes
Resources removed from the tree leave their target copies in place and are listed as note: lines. --prune plans them as delete — only what the map holds, never anything else in the target — under the same plan-time lock as push --prune (a GET, refused on lastModified drift, --force-remote overrides, re-checked right before each DELETE), and asks Delete N target copies from '<name>'? [y/N]; -y skips the prompt, and declining skips the deletes while the rest is promoted. The map entry is dropped on success (deleted or already gone).
⚠️
promote --prunedeletes resources from the target account. Read thedeletelines ofceligo promote --to <name> --prune --dry-runfirst.
The deletes run after the updates: when the source removes a flow and the integration alias that named it in one commit, the alias leaves the target integration first, and the flow's DELETE goes through (the platform refuses to delete a flow an alias still names). An alias added in the target's UI that still names a pruned flow fails that delete with the way out — remove the alias there, then prune.
Interrupting a promote
Ctrl-C once finishes the in-flight request and stops with the report (exit 130); twice aborts. Creates are interrupt-safe against the target: the intent (type, name, path, time, owner) is durable in the target file before the POST and cleared on landing, so the next promote adopts what an interrupted run created — by name, time window, and content — as recovered instead of creating a second copy, or refuses with a sentence when it cannot tell. A copy the interrupted run's own PUT already brought up to date is recognized on resume, so a crash never demands --force-remote for promote's own writes.
What promote does not do in this version
Neutral trees (
pull --neutral) are not promotable; promote works from a standard tree. The one-branch-per-environment setup is their alternative.Integration Apps. An app's documents (
_connectorId) arewithheld; install the app in the target and promote your own resources.Celigo Storage is pull-only in the tree and skipped by promote.
Day to day
diff --target only reads the target, so it runs in whatever mode the target profile is in — a read-mode production profile on a reviewer's machine serves it. Promote itself needs full mode. The CI job that promotes on merge, with the target's own token, is in Teams, git, and CI.
Related
Push changes — the same plan, the same guards, against the tree's own account.
Status and diff —
diff --target <name>.Neutral trees — the other multi-environment model.
Teams, git, and CI — the promote job and the two tokens.
connections —
getandupdatefor entering a shell's credentials.
Last updated
Was this helpful?