Move a gateway between environments
Add a QA environment, protect it, require provider evidence before a deploy, retire it cleanly, and promote a configuration with review.
On this page
Your workspace starts with Development and Production. This page adds the ones you need in between, decides who may deploy where, and moves one gateway’s configuration from one environment to another with a review you can read before anything activates.
You need workspace.manage to create, rename or retire an environment, config.edit to
write a draft or a release, and deploy to activate one. A protected environment adds
deploy.protected. Who can do what in your company
covers how those grants reach a person.
Each section carries an Availability line, because the operations behind this page land across several increments of this private test.
Find an application’s environments
App Home, the gateway environment selector and promotion show the environments enabled
for the selected application. An application developer can read these with app.read;
workspace-wide read access is not required. Creating, renaming and retiring environments
still require workspace.manage, and selecting a protected target does not grant deployment
permission.
Use cloud env list --org <organization> --app <application> for the same scoped list.
The TypeScript SDK exposes client.applications.environments(org, app) and Python exposes
client.applications.environments(org, app). Both call
GET /organizations/{org}/applications/{app}/environments in the customer API and support
pagination. Omitting --app retains the workspace list, which requires workspace.read.
Add a QA environment
An environment is an ordinary row, not a fixed enumeration, so you add one when two sets of traffic need different credentials, different limits or different authority.
Create it with a lowercase selector and a display name. Mark it protected at creation if deploying there should need a second grant.
$ tokentriage cloud env create --base-url https://app.example.com \
--org org_… --workspace t_… \
--selector qa --display-name "QA"
That command has not been run against the private deployment; the flags come from the
CLI’s own definitions. The management key travels in TOKENTRIAGE_MANAGEMENT_KEY, not in
argv.
Two fields are fixed at creation and cannot be changed afterwards: selector and
protected. The update operation has no field for either, so a later decision to protect
an environment is a new environment, not an edit.
Creating an environment enables it for every active application in that workspace. It does not deploy a gateway or issue a credential.
Availability. Environment create, update and the reference report are landed lane operations. In the portal the same form is Add environment on an application’s home, under Environments: an environment name, a permanent selector, and a checkbox reading “Deploying here needs explicit protected-environment permission”. The checkbox is on the create form and not on the rename form for the reason above.
Rename it without moving anything
A display name is the only thing a rename changes. Pass the revision you read, so a concurrent change is declined rather than overwritten.
$ tokentriage cloud env update --base-url https://app.example.com \
--org org_… --env env_… \
--display-name "QA (EU)" --expected-revision 3
That command has not been run against the private deployment either.
What a rename leaves alone is worth stating, because this is the question support gets:
the environment’s id, its selector, its protected flag, its issued keys, its provider
connection grants, its drafts and its applied release all stay as they were. The store’s
update statement reaches display_name, state and the provider-test gate, and no other
column.
Decide who may deploy to it
Protection is an authority statement, not a lifecycle state. A protected environment
admits a deploy only from a caller holding deploy and deploy.protected; a caller
with deploy alone is declined with 403 environment_protected_deploy_denied, and no
retry by that caller will succeed.
| In this environment | deploy alone |
deploy and deploy.protected |
|---|---|---|
| unprotected | deploys | deploys |
| protected | 403, by name | deploys |
Creating a release is a different decision and needs only config.edit. That split is
what lets a developer prepare a Production candidate that somebody else activates.
Availability. The deploy authority check lands with the deployment increment. Until then a protected environment has nothing to refuse.
Require a provider test before a deploy
An environment can insist that a release carry evidence that its connections work before
it may be deployed there. The flag is require_fresh_provider_test, it is off unless you
turn it on, and it lives on the environment rather than on the release: the same release
may go to QA without evidence and to Production with it.
{
"display_name": "Production",
"require_fresh_provider_test": true,
"expected_revision": 7
}
That body is the customer.environments.update request under workspace.manage. It has
not been sent against the private deployment. cloud env update carries no flag for the
gate yet, so set it from the portal or the lane.
In the portal the flag is a switch on the environment’s row, labelled “Deploying here needs a fresh provider test”, and it reads its position from the environment rather than from what you last clicked. Its own hint states the narrower claim when it is on: a release may be deployed here only if a provider test was recorded against that exact release’s digest, and a test of an earlier version of the configuration does not count. The switch sends the flag on its own. A rename sends the display name on its own. Neither request can express the other’s change, so renaming an environment cannot switch its deploy gate off.
The gate reads the evidence recorded against the release’s own effective digest, and answers with the two words that tell you which recovery you need.
| Answer | Status | What to do |
|---|---|---|
provider_test_required |
409 | run a provider test for this release’s connections, then deploy |
provider_test_stale |
409 | a test exists for a different digest; re-run it against this release |
A deploy admitted with nothing recorded returns the evidence the gate read, so a deploy with no test behind it does not read as one that passed.
Availability. The flag and the reference report are landed; the deploy gate that reads them lands with the deployment increment.
Retire one without breaking something
Retirement is a two-step lifecycle, and the second step is the one that can be declined.
active to retiring closes new work: a draft save, a release create and a deploy into
the environment are all answered 409 environment_not_active. retiring to retired
closes the environment, and it is declined while anything still points at it.
Read what points at it first, and the refusal holds no surprises.
$ tokentriage cloud env references --base-url https://app.example.com \
--org org_… --env env_…
That command has not been run against the private deployment.
The report groups by ledger, and every group is a named list rather than a count, so you learn which gateway is still deployed and which credential is still live.
| Group | What blocks retirement | How to clear it |
|---|---|---|
Deployments (deployment_heads) |
a gateway still has a desired or applied release here | roll the gateway back or move it to another environment |
Runtime keys (runtime_credentials) |
a live key admits traffic in this environment | revoke the key. Retiring the environment does not revoke it for you |
Provider connections (provider_grants) |
a saved connection is granted to this environment | revoke the grant for this environment |
Unpublished configuration (draft_heads) |
a gateway still holds an editable draft here | nothing is serving; the draft is what the report names, and clearing it is the gateway’s own cleanup |
A retired attempt while any group is non-empty comes back 409 environment_has_references with this same report inside the problem body, so one request
tells you both that it stopped and what to remove.
Three sentences the report can produce are worth telling apart, because two of them look
like emptiness and only one is. A group the server listed as empty is the server saying
nothing of that kind points here. A step that was admitted and carried no report at all is
silence, and the portal says that rather than drawing four empty lists. And a deployment
that wired no reference readers answers 503 dependency_unavailable, which means nobody
can tell you from here: ask whoever runs the deployment before you finish retiring.
Availability. Landed. In the portal the two steps are Retire this environment, which opens a panel, then Begin withdrawing this environment; the panel’s own button Check what points at this environment takes the report first, and the second step is Finish retiring it. The panel states the one-way rule before either step: there is no control that brings an environment back, and the way back is a new environment with a new selector. A retired row offers no step at all.
Promote a configuration, with the review first
Promotion moves a configuration, not a credential. The review is a separate operation from the apply so you can read what would happen while nothing has happened.
Ask for the review with the source subject, which is either a saved draft revision or an applied release, and the destination environment. The answer carries five things worth reading in this order.
- The destination plan and its diff against what is applied there now, with the destination’s effective digest.
- Every slot the plan needs, each with the destination connections that are eligible for it.
- The destination’s verdicts: inventory eligibility, mandatory-controls generation and current draft revision.
- The source’s provider-test evidence, with whether it still applies.
- Every blocker, and a
readyflag that stays false while one stands.
The connection mapping is the part people expect to be automatic. It is not: the source’s
connection is shown for reference and the destination’s binding is a choice you make from
the eligible list. A slot nobody mapped is present in the review and carries
connection_slot_unbound rather than quietly inheriting Development’s key.
Two renderings in that answer say less than a reader might assume, and that is the point of them. With no test recorded, the review reads “No
provider test recorded” instead of an implied pass. When a mapped connection’s provider,
endpoint or declared account label has moved since the evidence was captured, the slot is
invalid with connection_context_changed, and the evidence stops applying: a test against
one account is not a test of another.
Then apply. The apply echoes the destination’s mandatory generation, its draft revision and
its head revision, so the plan somebody read and the plan somebody applies are the same
plan, and a destination that moved in between loses with the word for what moved. It writes
a promoted draft revision into the destination, derives a release from it, and deploys
that release.
Rolling back is a separate operation, because a prior release was compiled in the past.
The head returns to a compatible earlier release after re-validating it under today’s
controls and grants; a release whose mandatory generation has been superseded, or whose
connection grant has been withdrawn, is declined 409 rollback_incompatible instead of
putting a withdrawn grant back into production. Nothing is recompiled: the digest stays
what it was, because re-deriving could activate a configuration nobody reviewed.
Availability. Promotion review and apply, deploy and rollback land with the promotion
and deployment increments. Their operation rows are already declared, and a deployment
with no release ledger behind them answers 503 with “deployments not configured”.
The portal drives review and apply from the gateway’s Configure tab, in Promote this configuration. It promotes a saved draft revision, so it says there is nothing to promote until you have saved one. Choosing a protected destination says so before you review. Each slot in the plan carries a Destination connection for … picker over that environment’s eligible connections, and a slot nobody mapped keeps the plan from applying. The evidence line reports what was recorded and whether it still applies, in those two separate words, and it does not say “passed”. The apply states the destination’s required-controls generation, its draft revision and its deployment head revision, so a destination that moved loses with the slug naming what moved.
When it is declined
Each answer below names one recovery. The status is fixed per slug, so a client can branch on the word rather than on prose.
| Slug | Status | What to do |
|---|---|---|
environment_not_active |
409 | the environment is retiring or retired; pick another one, or set it back to active |
environment_has_references |
409 | clear the groups the attached report lists, then retire it |
environment_protected_deploy_denied |
403 | ask somebody with deploy.protected here, or have the grant added |
provider_test_required |
409 | record a provider test for the release’s connections |
provider_test_stale |
409 | re-run the test against this release’s effective digest |
connection_context_changed |
409 | re-review: the destination credential’s account moved, so re-map and re-test |
mandatory_generation_stale |
409 | re-review: required controls changed between the review and the apply |
connection_slot_unbound |
422 | map every slot in the review before applying |
rollback_incompatible |
409 | choose a different prior release, or promote forward instead |
head_revision_stale |
412 | read the deployment head again and echo the revision it returns |
connection_not_eligible |
403 | grant the connection for this environment and purpose, or pick an eligible one |
Why a stale provider test is a separate answer from a missing one
They have different recoveries and different meanings. Nothing recorded means the connections have not been exercised at all. A stale one means somebody did the work against a different configuration, so the evidence exists and describes something else. Folding both into one word would send half the callers to the wrong fix.
Review and apply from the CLI
Save a plan containing the source selector (including its environment_id) and
explicit destination bindings to promotion-review.json. The path environment is
always the destination:
$ tokentriage cloud gateway promote review --base-url https://app.example.com \
--org org_… --workspace t_… --app app_… --gateway gw_… \
--env env_qa --plan-file promotion-review.json --json
Read every blocker and mapped connection context. For apply, save the same source and
bindings with each binding’s reviewed expected context, plus
expected_mandatory_generation, expected_draft_revision, and
expected_head_revision from the destination review and deployment head. All three
must be present; zero is meaningful for a destination never deployed before.
$ tokentriage cloud gateway promote apply --base-url https://app.example.com \
--org org_… --workspace t_… --app app_… --gateway gw_… \
--env env_qa --plan-file promotion-apply.json --yes --json
--yes is required even with --json. Apply writes the destination draft and desired
release; inspect the head afterwards to establish that it was actually applied.
A stale revision requires a new review, not replaying the old plan. Neither command
executes a provider request, and neither bypasses protected-deployment permissions or
a required fresh provider test. Management-key authorization is resolved for each request.
What this page does not cover
- Editing the configuration you are promoting: Configure, test and promote a gateway.
- The document, the operation table and every slug’s status: Gateway configuration reference.
- Creating and testing the connections a slot binds: Add a saved provider connection.