Replace an Environment's configuration from a JSON payload (destructive)
/api/v1/environments/{id}/importReplaces the configuration of the Environment :id with the supplied export document. This is destructive: existing roles, permissions, nodes, OAuth clients, webhook subscriptions, and pending invites in the target are wiped before the payload is applied within a single transaction; organization role templates are matched by name, a template the target already has updated in place so its organizations' copies keep naming it, and the target's others deleted; identities, memberships, and assignments survive except where cascading role/node deletes remove dangling rows. The payload must declare a supported format_version (400 otherwise) and contain exactly one root node, with duplicate keys, cyclic parents, orphaned nodes, and unknown permission references each rejected as 400. Every imported role lands as a custom role whatever the payload's is_system_role says, as a permission lands as custom unless its key is reserved. Returns 200 with one-time OAuth client and webhook signing-secret rotation receipts and emits an environment.imported audit event; 404 when no Environment of the Account has that id.
Authentication
Authorization Option A JWT access token. Never send alongside X-API-Key: a request carrying both is refused.
X-API-Key Option B API key for management-tier access. Never send alongside an Authorization header: a request carrying both is refused.
application.manageAccountCreate, update, and delete Applications and Environments, and change Environment settings. Every one of these is checked. A scoped API key must hold each as a scope; a developer's token needs the Console capabilities that cover them.
auth_settings.manageAuthenticationChange the Environment's sign-in settings. Held only when named: full access does not include it. Every one of these is checked. A scoped API key must hold each as a scope; a developer's token needs the Console capabilities that cover them.
Path Parameters
idstring Required Request body
application/json
format_versionnumber Required source_envobject Required access_modelenum Required organizations_enabledboolean Required hierarchy_schemaobject Required auth_settingsobject Required The sign-in policy, replacing the target's: a setting left out returns to its default. The target keeps its web origins, return URLs, sign-in addresses, passkey relying party, machine-token audience and the settings naming its roles.
token_claimsTokenClaimEntryDto[] Required The custom claims, replacing the target's; an empty list removes them
permissionsany[] Required rolesany[] Required organization_role_templatesany[] Required The organization role templates, replacing the target's: a template whose name the target already has is updated in place, so its organizations' copies keep naming it
nodesany[] Required oauth_clientsany[] Required webhooksany[] Required metadataobject Optional Responses
application/json
data *EnvironmentCloneResponseDto
application/json
error *ApiErrorBodyDto
application/json
error *ApiErrorBodyDto
application/json
error *ApiErrorBodyDto
application/json
error *ApiErrorBodyDto
Errors
When the request can't be completed, the response body includes a stable error code you can branch on, and a correlation_id (also sent as the X-Correlation-Id header). Quote the correlation id when you contact support: it identifies the exact request in Canopy's logs. A request refused by an authorization check answers 403 with the code authorization.denied, and its data names the denial_reason and the permission checked.
authorization.deniedForbiddenThe credential does not hold the permission this operation checks: a scoped API key without the scope, a developer without the Console capability that covers it, or a permission granted somewhere other than the node the request targets. The error's data names the permission.
Add the permission named in the Authentication section to the key's scopes, or ask an account administrator to grant the covering capability, then retry. For a node-scoped operation, make sure the grant reaches the node you are acting on.
Returned object
curl -X POST "https://auth.canopy-io.com/api/v1/environments/value/import" \ -H "X-API-Key: $CANOPY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "format_version": 1, "source_env": {}, "access_model": "flat", "organizations_enabled": false, "hierarchy_schema": {}, "auth_settings": { "mfa_required": false, "mfa_grace_period_days": 0, "mfa_trusted_device_days": 0, "mfa_factor_allowlist": [ "totp" ], "mfa_after_sso": "exempt", "allow_self_signup": false, "self_signup_require_captcha": false, "login_require_captcha": false, "password_login_enabled": false, "email_otp_login_enabled": false, "passkey_login_enabled": false, "self_signup_daily_cap": 0, "access_token_ttl_seconds": 0, "refresh_token_ttl_days": 0, "public_refresh_token_ttl_days": 0, "m2m_access_token_ttl_seconds": 0, "organizations_create_on_signup": false, "organizations_membership_required": false }, "token_claims": [ { "claim": "plan", "source": "identity.metadata.plan", "tokens": [ "access", "id" ] } ], "permissions": [ null ], "roles": [ null ], "organization_role_templates": [ null ], "nodes": [ null ], "oauth_clients": [ null ], "webhooks": [ null ], "metadata": {} }'
{ "data": { "environment": { "id": "string", "application_id": "string", "name": "string", "slug": "string", "is_production": false, "settings": {}, "version": 0, "created_at": "2026-04-20T12:00:00.000Z", "updated_at": "2026-04-20T12:00:00.000Z" }, "oauth_client_rotations": [ { "source_client_id": "string", "client_id": "string", "client_secret": "string" } ], "webhook_subscription_rotations": [ { "id": "string", "url": "string", "secret": "string" } ] } }
Tell us how we can improve this guide.