Environments
Manage a tenant's dev/staging/production environments — lifecycle, the default and production flags, protection policy, readiness validation, comparison, and artifact/provider bindings.
A tenant is one isolation boundary; an environment
is a partition within that boundary. Environments let a single tenant separate
development, staging, and production work — each with its own artifact
bindings, provider bindings, credential-visibility rules, and mutation policy —
so that promoting a workflow from staging to production is a governed step rather
than an in-place edit to live config.
Every tenant has exactly one default environment; unqualified reads and
writes resolve against it. Production environments are additionally marked with a
production flag that tightens what may change and how.
Where it lives
Environments are served by EnvironmentService under /api/v2/environments.
The pure-domain contracts are in domain/environment/types.go; the application
aggregate is application/environment/service.go. Reads require
environment:read, mutations require environment:write, and toggling the
production flag requires the elevated environment:admin scope.
The environment object
Prop
Type
The protection policy
EnvironmentPolicy is the knob-set that governs whether changes to an environment
are allowed directly, must go through promotion, and how credentials are exposed.
When you create an environment without a policy, Ductor derives sensible defaults
from the purpose.
Prop
Type
Read the knobs as a gate sequence rather than a flat list — they answer one question in order, and a production environment simply has stricter answers:
The defaults are purpose-driven. A non-production environment defaults to
mutable, direct mutation allowed, live provider calls allowed,
metadata_only credential visibility, and scoped_writers. A production
environment flips to: no direct mutation, promotion required, certification
required, redacted credentials, and promotion_only writes. The domain
further rejects incoherent production policies — for example, a production
environment that allows direct mutation but does not require certification is
invalid.
Lifecycle
Create
CreateEnvironment — POST /api/v2/environments (environment:write). Only
key is required; display_name, purpose, policy, and default are
optional. Responses carry metadata and policy only — credentials, API-key
plaintext, and provider tokens are never returned.
curl -s -X POST https://api.ductor.io/api/v2/environments \
-H "Authorization: Bearer $DUCTOR_TOKEN" \
-H "X-Tenant-ID: $DUCTOR_TENANT_ID" \
-H "Content-Type: application/json" \
-d '{ "key": "staging", "display_name": "Staging", "purpose": "staging" }'Read, update, list
GetEnvironment (GET /api/v2/environments/{environment}), ListEnvironments
(GET /api/v2/environments), and UpdateEnvironment
(PATCH /api/v2/environments/{environment}) cover reads and metadata/policy
edits. ListEnvironments filters by status, purpose, production_only, and
include_archived. The {environment} path segment accepts either the key or
the ID.
Set the default
SetDefaultEnvironment — POST /api/v2/environments/{environment}:set-default
(environment:write) — moves the tenant's default. Because a tenant must always
have exactly one default, this is an atomic switch, not an additive flag.
Set the production flag
SetProductionFlag — POST /api/v2/environments/{environment}:set-production
(environment:admin) — marks or unmarks an environment as production and updates
its policy semantics atomically. This is the one environment operation that
requires environment:admin rather than environment:write.
curl -s -X POST "https://api.ductor.io/api/v2/environments/production:set-production" \
-H "Authorization: Bearer $DUCTOR_TOKEN" \
-H "X-Tenant-ID: $DUCTOR_TENANT_ID" \
-H "Content-Type: application/json" \
-d '{ "environment": "production", "production": true }'Archive
ArchiveEnvironment — POST /api/v2/environments/{environment}:archive
(environment:write) — archives a non-default environment. Archived
environments fail closed for mutable artifact bindings: they stop accepting the
artifact changes an active environment would.
Readiness and comparison
Environments exist so that promoting change is deliberate. Two operations support that governance directly.
Validate readiness
ValidateEnvironmentReadiness —
POST /api/v2/environments/{environment}:validate-readiness (environment:read)
— answers "can this environment safely accept governed changes right now?" It
returns an EnvironmentReadinessReport:
Prop
Type
Use it as the gate before a promotion: a non-empty blocking_reasons means the
target environment is not in a state to receive the change.
Compare two environments
CompareEnvironments — GET /api/v2/environments:compare?from={a}&to={b}
(environment:read) — diffs the artifact bindings between two environments and
returns redacted added / removed / changed bindings, an allowed verdict,
and any blocking_reasons. This is what you inspect before promoting to see
exactly what would move.
curl -s "https://api.ductor.io/api/v2/environments:compare?from=staging&to=production" \
-H "Authorization: Bearer $DUCTOR_TOKEN" \
-H "X-Tenant-ID: $DUCTOR_TENANT_ID"Bindings
An environment is bound to the artifacts and providers it runs.
- Artifact bindings —
ListEnvironmentArtifactBindings(GET /api/v2/environments/{environment}/artifact-bindings,environment:read) — list the redacted artifacts pinned to the environment: workflow definitions, connector deployments and connections, runtime-config refs, policy packs, promotion candidates, schedule triggers, and more. The runtime-config ref kind is how a resolved runtime configuration is attached to an environment. Filter byartifact_kindandartifact_id. - Provider bindings —
ListProviderEnvironmentBindings(GET /api/v2/environments/{environment}/provider-bindings,environment:read) — list explicit provider bindings for sandbox, staging, and production environments. Provider credentials and tokens are never returned. Filter bycanonical_provider_key,purpose, andstatus(available,needs_review, orunsupported).
Provider-binding validation enforces environment hygiene: a production binding
may not execute a sandbox or staging provider key, and sandbox/staging bindings
must record a distinct provider key when marked available. These guards keep a
"production" environment from quietly pointing at non-production infrastructure.
Operations at a glance
| Operation | Method & path | Scope |
|---|---|---|
| List environments | GET /api/v2/environments | environment:read |
| Create environment | POST /api/v2/environments | environment:write |
| Get environment | GET /api/v2/environments/{environment} | environment:read |
| Update environment | PATCH /api/v2/environments/{environment} | environment:write |
| Set default | POST /api/v2/environments/{environment}:set-default | environment:write |
| Set production flag | POST /api/v2/environments/{environment}:set-production | environment:admin |
| Archive | POST /api/v2/environments/{environment}:archive | environment:write |
| Validate readiness | POST /api/v2/environments/{environment}:validate-readiness | environment:read |
| Compare | GET /api/v2/environments:compare | environment:read |
| List artifact bindings | GET /api/v2/environments/{environment}/artifact-bindings | environment:read |
| List provider bindings | GET /api/v2/environments/{environment}/provider-bindings | environment:read |
Where to go next
Cases
Manage cases end to end — the create → assign/claim → resolve/escalate lifecycle plus comments, tasks, attachments, the event timeline, and table-row links.
Secrets, Variables & Contexts
The tenant config and data plane — encrypted Secrets, plaintext Variables, and the composite-keyed Context JSON store — with a disambiguation table and the Context-store name-collision warning.