Terraform Provider
Terraform is an infrastructure-as-code tool that lets you define your environment configuration in .tf files and apply it consistently across development, staging, and production. Instead of configuring environments by hand, you declare a desired state and let Terraform manage it. You can read more in the Terraform introduction.
Descope publishes a Terraform provider for managing projects and their configuration.
Configurations written for provider versions before v1
Provider v1 introduced a new model, so configurations written for earlier versions might need substantial changes to work with it. Earlier versions nested project settings inside descope_project, in blocks such as authentication = {...} or connectors = {...}.
If your configuration uses those blocks, follow the upgrading from v0.3 guide before you apply the examples below.
Important
Terraform is best suited for managing infrastructure and configuration that should be consistent across environments. Dynamic elements of a project, such as individual users, tenants, SSO connections, and SCIM configurations, are not typically managed by Terraform. These are unique to each project or environment and are generally handled through the Descope Console, SDKs, or APIs, and not as infrastructure-as-code.
Project-level SSO settings, SSO Setup Suite options, and the hosted Admin Portal are managed in Terraform. See SSO Settings and Admin Portal below.
How Resources Map to a Project
descope_project manages only the project itself: its name, environment, tags, and deletion_protection. Everything inside the project has its own resource. Settings resources such as descope_session_settings are singletons, so each project has at most one of each. Entity resources such as descope_role or descope_http_connector describe one object each.
Terraform only manages the objects you declare. If your configuration has no descope_sso_settings resource, Terraform leaves the project's SSO settings alone. If you declare a settings resource, Terraform manages every attribute of it, and an apply can reset attributes you leave out to the provider defaults.
Import existing settings before you manage them
Creating a settings resource, descope_styles, descope_fga_schema, a flow, or a widget in an existing project overwrites the configuration already there. Import these objects first, as described in Importing an Existing Project. The provider warns during planning when one of them is about to be created in an existing project.
Prerequisites
The Terraform provider requires a paid Descope license (Pro+). Contact support@descope.com with any licensing questions.
- Terraform CLI 1.0 or later installed. Adopting an existing project with
importblocks needs 1.5 or later. - A Management Key from Company Settings. To create or manage projects via Terraform, the key needs a company-level role with the appropriate permissions (see Management Key Roles).
Using the Terraform Provider
Provider Configuration
The required_providers block below tells Terraform to download the Descope provider and pins its version:
terraform {
required_providers {
descope = {
source = "descope/descope"
version = "~> 1.0"
}
}
}Warning
Never hardcode your management key in Terraform configuration files, because doing so risks exposing it in version control. Use environment variables or a secrets manager instead.
The provider reads its credentials from these environment variables:
| Variable | Description |
|---|---|
DESCOPE_MANAGEMENT_KEY | A valid management key for your Descope company |
DESCOPE_BASE_URL | Override the Descope API base URL (optional, for testing) |
You can export the management key in the shell that runs Terraform:
export DESCOPE_MANAGEMENT_KEY="K2..."With those set, the provider block needs no additional configuration:
provider "descope" {}You would then run terraform init to download the provider:
terraform initIf you need to configure credentials explicitly, for example in a module, you can pass the key as a sensitive variable:
variable "descope_management_key" {
type = string
sensitive = true
}
provider "descope" {
management_key = var.descope_management_key
}Creating a Project
The descope_project resource creates the project. Other resources reference it through descope_project.myproject.id.
resource "descope_project" "myproject" {
name = "project-name"
environment = "production"
tags = ["foo", "bar"]
}Attributes like tags support dynamically computed values. The next example appends tags from a variable:
variable "additional_project_tags" {
type = list(string)
nullable = false
}
resource "descope_project" "myproject" {
name = "project-name"
tags = [
"foo",
...var.additional_project_tags
]
}Deletion protection
The provider protects projects from deletion by default. A terraform destroy of a descope_project fails until you set deletion_protection = false and apply that change first. Federated apps and inbound apps have the same protection.
Importing an Existing Project
If you already have a Descope project and want to bring it under Terraform management, you can generate the configuration from the live project instead of writing it by hand. Because the provider splits a project into many resources, the provider repository ships a tool called tfexport that writes all of them at once.
Note
This workflow requires Terraform CLI 1.5 or later, because import blocks are not available in earlier versions.
1. Export the project
The tfexport tool reads your project and writes a directory of .tf files, with one resource for each supported object and an import block for each resource. Its README explains how to install it. The command below exports project P... into ./my-project:
export DESCOPE_MANAGEMENT_KEY="K..."
tfexport -project P... -out ./my-projectIf your configuration already has a descope_project resource, you can pass -project-address descope_project.myproject so the generated resources reference it instead of declaring a new project.
tfexport doesn't export access keys, management keys, Descopers, engines, users, or tenants.
2. Fill in secrets
The Descope API never returns secrets such as connector credentials, so tfexport declares them as sensitive variables. You would set their values in a terraform.tfvars file that you keep out of version control, or as TF_VAR_<name> environment variables.
3. Plan and review
Run a plan and check that it imports everything and changes nothing except secrets:
terraform planPlans that update secrets in place are normal. The API reports that the project holds a secret but not its value, so each imported resource with a stored secret plans an update that resends it from its variable. You should not apply the plan if it creates a resource that already exists, changes a value you configured, or clears a secret.
4. Apply to import the project into state
Once the plan looks right, apply it:
terraform applyThe import blocks tell Terraform to attach the existing objects to state rather than create new ones. Once the apply succeeds, you can delete the import blocks, and later plan and apply runs manage the project as usual.
Importing a single resource
You can also adopt one object at a time with an import block and terraform plan -generate-config-out="generated.tf". The import ID depends on the resource's shape, as the following table shows.
| Resource shape | Import ID | Examples |
|---|---|---|
| Project and company-level resources | <id> | descope_project, descope_management_key, descope_descoper |
| Singleton settings | <project_id> | descope_project_settings, descope_sso_settings, descope_styles, descope_fga_schema, descope_admin_portal |
| Entity in a project | <project_id>/<id> | descope_role, descope_http_connector, descope_flow, descope_inbound_app, descope_access_key, descope_engine |
| Entity scoped to a method or app | <project_id>/<scope>/<id> | descope_email_template (<project_id>/invite/<template_id>), descope_app_role |
The import block below adopts an existing project's SSO settings:
import {
to = descope_sso_settings.main
id = "P..." # your project ID
}Inbound apps and client_type
When you import an existing inbound app, you can leave client_type out, and Terraform keeps the app's current type. If you do set it, set it to the app's current type, because some changes replace the app with a new client ID and secret. See Inbound App Client Type Changes.
Examples
Each example below is a standalone resource that references descope_project.myproject through project_id.
Project Settings
Project-level settings are split across two singleton resources. descope_project_settings holds domain, security, and test user settings. descope_session_settings holds token lifetimes and inactivity rules.
resource "descope_project_settings" "main" {
project_id = descope_project.myproject.id
app_url = "https://app.example.com"
approved_domains = ["example.com", "app.example.com"]
}
resource "descope_session_settings" "main" {
project_id = descope_project.myproject.id
refresh_token_expiration = "3 weeks"
session_token_expiration = "15 minutes"
refresh_token_rotation = true
enable_inactivity = true
inactivity_time = "1 hour"
}Full project settings schema reference · Full session settings schema reference
Invite Settings
The descope_invite_settings resource controls user invitation behavior. A custom invitation email is a separate descope_email_template resource with method = "invite", which the invite settings select by ID.
resource "descope_email_template" "invite" {
project_id = descope_project.myproject.id
method = "invite"
name = "Invite Email"
subject = "You've been invited"
html_body = "<p>Click <a href='{{link}}'>here</a> to accept your invitation.</p>"
}
resource "descope_invite_settings" "main" {
project_id = descope_project.myproject.id
require_invitation = true
invite_url = "https://example.com/invite"
add_magiclink_token = true
expire_invited_users = true
invite_expiration = "2 weeks"
send_email = true
email_template_id = descope_email_template.invite.id
}The expire_invited_users flag causes invited user accounts to expire if the invitation is not accepted within the invite_expiration duration. The invite_expiration field accepts human-readable durations such as "2 weeks" or "4 days", with a minimum value of "1 hour".
To send invitations through your own email provider, you can set email_connector_id to the ID of an email connector resource. An empty value selects the built-in Descope delivery service.
Full invite settings schema reference · Full email template schema reference
Authorization
Each permission and each role is its own resource. A role lists its permissions by name, so referencing descope_permission.<name>.name lets Terraform create the permission before the role.
resource "descope_permission" "read_data" {
project_id = descope_project.myproject.id
name = "read:data"
description = "Read access to application data"
}
resource "descope_role" "viewer" {
project_id = descope_project.myproject.id
name = "viewer"
description = "Can read data"
permissions = [descope_permission.read_data.name]
}Full role schema reference · Full permission schema reference
FGA Schema
The descope_fga_schema resource manages your project's Fine-Grained Authorization schema as code. In the Descope console, you can open the Authorization > FGA page and copy the schema text from the editor. The example below loads the schema from a file:
resource "descope_fga_schema" "main" {
project_id = descope_project.myproject.id
schema = file("${path.module}/fga-schema.txt")
}Note
The schema value must start with model AuthZ. Setting an empty schema, or destroying the resource, clears the project's FGA schema and removes any FGA relations that depend on it.
Authentication
Each authentication method has its own singleton settings resource. The example below configures magic link expiration and password policy:
resource "descope_magiclink_settings" "main" {
project_id = descope_project.myproject.id
expiration_time = "1 hour"
}
resource "descope_password_settings" "main" {
project_id = descope_project.myproject.id
lock = true
lock_attempts = 3
min_length = 8
disallow_email_match = true
disallowed_characters = "'\""
enforce_strength = "very_strong"
}The other methods follow the same pattern with descope_otp_settings, descope_enchantedlink_settings, descope_embeddedlink_settings, descope_totp_settings, descope_oauth_settings, and descope_sso_settings. Custom social login providers use descope_oauth_provider.
Full magic link settings schema reference · Full password settings schema reference
Passkeys
The descope_passkey_settings resource configures passkey authentication. The android_fingerprints field restricts which Android apps may use passkeys by their SHA-256 APK key hash fingerprint (colon-separated hex format). An empty list places no restriction on Android apps.
Once you list a fingerprint, only the Android apps you list can use passkeys, so you should include every signing key your apps use. Web and iOS clients are unaffected. See Passkeys Settings for details.
resource "descope_passkey_settings" "main" {
project_id = descope_project.myproject.id
top_level_domain = "example.com"
display_name = "Example App"
android_fingerprints = [
"AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89"
]
}Full passkey settings schema reference
Authentication Access Policy
The descope_auth_access_rule and descope_auth_access_policy resources manage the rules that allow or block an authentication based on the request's IP address or country, or the user's email, login ID, or phone. The policy also sets the order in which Descope evaluates the rules.
Full auth access rule schema reference · Full auth access policy schema reference
Attributes
Custom user and tenant attributes are separate resources. The id attribute is the machine name of the attribute, and changing it replaces the resource.
resource "descope_user_attribute" "department" {
project_id = descope_project.myproject.id
id = "department"
name = "Department"
type = "string"
}
resource "descope_tenant_attribute" "plan" {
project_id = descope_project.myproject.id
id = "plan"
name = "Plan"
type = "multiselect"
select_options = ["A", "B"]
}Access key attributes use descope_access_key_attribute.
Full user attribute schema reference · Full tenant attribute schema reference
Connectors
Each connector type has its own resource named after the type, such as descope_http_connector or descope_aws_s3_connector. The example below shows bearer token auth on an HTTP connector, role-based auth on an S3 audit connector, and a reCAPTCHA Enterprise connector:
# Bearer token authentication
resource "descope_http_connector" "eligibility" {
project_id = descope_project.myproject.id
name = "Test HTTP"
description = "A Description"
base_url = var.http_connector_base_url
use_static_ips = false
authentication = {
bearer_token = var.http_connector_secret
}
}
# Role-based authentication
resource "descope_aws_s3_connector" "audit" {
project_id = descope_project.myproject.id
name = "S3 Audit Connector"
description = "A Description"
auth_type = "assumeRole"
role_arn = "arn:aws:iam::YOUR_ACCOUNT_ID:role/your-connector-role"
external_id = "YOUR_EXTERNAL_ID"
region = "us-east-1"
bucket = "your-audit-logs-bucket"
audit_enabled = true
}
# reCAPTCHA Enterprise
resource "descope_recaptcha_enterprise_connector" "bot_protection" {
project_id = descope_project.myproject.id
name = "reCAPTCHA Enterprise"
description = "Bot protection"
config_project_id = var.recaptcha_project_id
site_key = var.recaptcha_site_key
api_key = var.recaptcha_api_key
action = "login"
}In the reCAPTCHA Enterprise connector, config_project_id is the Google Cloud project ID, and project_id is the Descope project.
Full HTTP connector schema reference · Full AWS S3 connector schema reference · Full reCAPTCHA Enterprise connector schema reference
JWT Templates
The descope_jwt_template resource configures a custom JWT claim template. You can include a description, control which standard claims are included, and add security features like JTI. The type attribute selects user session JWTs (user) or access key JWTs (key).
resource "descope_jwt_template" "app_claims" {
project_id = descope_project.myproject.id
name = "app-claims"
description = "Adds subscription tier and org context to user JWTs"
type = "user"
template = jsonencode({
tier = "@user.customAttributes.subscriptionTier"
org_id = "@user.tenants[0].tenantId"
})
# Exclude the permissions claim to keep tokens lean
exclude_permission_claim = true
# Add a unique JWT ID for replay attack prevention
add_jti_claim = true
# Move the user ID to a new dsub claim, allowing sub to be customized
override_subject_claim = true
}A template takes effect when the session settings reference it by ID. You would set user_jwt_template = descope_jwt_template.app_claims.id on descope_session_settings, or access_key_jwt_template for a key template.
Full JWT template schema reference
SSO Settings and Admin Portal
Terraform manages project-level SSO with descope_sso_settings, which also holds the SSO Setup Suite options in sso_suite_settings. The hosted Admin Portal has its own descope_admin_portal resource.
The table below shows where each SSO concern lives:
| In Terraform | Not in Terraform (use Setup Suite, Console, or Management API) |
|---|---|
Project SSO auth method settings (descope_sso_settings) | Per-tenant SAML/OIDC connections (IdP metadata, certs, maps) |
SSO Setup Suite UI options (sso_suite_settings) | Per-tenant SCIM tokens and base URLs |
Default post-auth redirect URL (redirect_url) | Individual tenants and users |
SSO invite email template (descope_email_template with method = "sso") | Day-to-day customer IdP changes |
Admin Portal enablement, style, and widget list (descope_admin_portal) |
You can automate per-tenant connections with the Management SSO SDKs or tenants SSO API, or hand customers the SSO Setup Suite.
Project SSO Settings
These attributes map to Project-level SSO settings in the Console.
resource "descope_sso_settings" "main" {
project_id = descope_project.myproject.id
# Disable direct SSO API/SDK starts (Flows that use SSO still work)
disabled = false
# Merge SSO logins into existing users with the same email
merge_users = true
# Default post-auth redirect (tenant, SDK, or API can override)
redirect_url = "https://app.example.com/auth/callback"
# Same SSO domain allowed on more than one tenant
allow_duplicate_domains = false
# SSO-mapped roles replace the user's existing roles
allow_override_roles = true
# Prefer group-based role maps when priorities conflict
groups_priority = true
# Tenant SSO configs must include an SSO domain
require_sso_domains = true
# Tenant SSO configs must set a groups attribute name
require_groups_attribute_name = true
# Reject login if the assertion email domain is not a configured SSO domain
block_if_email_domain_mismatch = true
# Leave email unverified after SSO
mark_email_as_unverified = false
# Allow converting a user into an SSO connection even if they belong to other tenants too
allow_merge_users_with_multiple_tenants = false
# Only allow mapping attributes listed below
limit_mapping_to_mandatory_attributes = false
# Attributes that must be present and mappable from the IdP
# (id = Console "Machine Name", set custom = true for custom attributes)
mandatory_user_attributes = [
{ id = "email" },
{ id = "name" },
{ id = "department", custom = true },
]
# SSO Setup Suite, see the next subsection
sso_suite_settings = {
style_id = "customer-facing-style"
hide_sso = false
hide_scim = false
hide_saml = false
hide_oidc = false
hide_role_mapping = false
hide_fga_mapping = false
hide_domains = false
hide_jit_guide = false
force_domain_verification = false
show_help_contact = true
support_email = "it-support@example.com"
}
# Optional: custom template for SSO invite emails
email_template_id = descope_email_template.sso_invite.id
}
resource "descope_email_template" "sso_invite" {
project_id = descope_project.myproject.id
method = "sso"
name = "SSO Invite"
subject = "You've been invited to sign in"
html_body = "<p>Click <a href='{{link}}'>here</a> to accept.</p>"
}The following table describes each attribute:
| Attribute | Purpose |
|---|---|
disabled | Blocks direct SSO via API/SDK. It does not disable SSO inside Flows. |
merge_users | Merge SSO users into existing accounts with the same email (merging guide) |
redirect_url | Project default post-auth URL (settings). Required for IdP-initiated unless set per tenant. |
allow_duplicate_domains | Allow the same SSO domain on multiple tenants |
allow_override_roles | Let SSO group to role maps override existing roles (details) |
groups_priority | Enable groups priority when resolving role maps |
require_sso_domains | Force an SSO domain on each tenant SSO config (settings) |
require_groups_attribute_name | Force a groups attribute name on each tenant SSO config |
block_if_email_domain_mismatch | Fail login when the email domain doesn't match the SSO domains |
mark_email_as_unverified | Do not mark email verified after SSO |
allow_merge_users_with_multiple_tenants | Allow converting a user into an SSO connection even if they belong to other tenants too (details) |
mandatory_user_attributes | Required Descope attributes from SSO (id, optional custom) |
limit_mapping_to_mandatory_attributes | Only allow maps into those mandatory attributes |
sso_suite_settings | Hosted Setup Suite UI (below) |
email_connector_id | ID of the email connector for SSO invite emails. An empty value selects the built-in Descope delivery service. |
email_template_id | ID of a descope_email_template with method = "sso" for SSO invite emails |
Full SSO settings schema reference
SSO Setup Suite Settings
The sso_suite_settings attribute of descope_sso_settings controls what customer IT admins see in the SSO Setup Suite. It mirrors the SSO Suite settings in the Console.
| Attribute | Purpose |
|---|---|
style_id | Style applied to the suite (must exist in the project) |
hide_sso | Hide the whole SSO configuration, leaving a SCIM-only suite |
hide_saml / hide_oidc | Hide SAML or OIDC setup |
hide_scim | Hide SCIM in the suite |
hide_groups_mapping | Hide the groups mapping UI |
hide_role_mapping | Hide only the role mapping UI |
hide_fga_mapping | Hide only the FGA mapping UI |
hide_domains | Hide SSO domains UI |
hide_jit_guide | Hide the JIT provisioning guide in the hosted UI |
force_domain_verification | Allow only verified domains |
oidc_login_id_attribute | The IdP claim the suite suggests as the Login ID for OIDC configurations (defaults to sub) |
show_xaa | Show the Cross-App Access (XAA) section, which stays hidden unless this is set |
show_help_contact | Show a help/support contact in the suite |
support_email | Email shown as that support contact |
Admin Portal
The descope_admin_portal resource configures the hosted Admin Portal, including which widgets appear and which style to use. Each widget_id usually references a widget managed with descope_widget (see Widgets).
resource "descope_admin_portal" "main" {
project_id = descope_project.myproject.id
enabled = true
style_id = "admin-portal-style"
widgets = [
{
type = "user-management-widget"
widget_id = descope_widget.user_management.widget_id
},
]
}The following table describes each attribute:
| Attribute | Purpose |
|---|---|
enabled | Turn the hosted Admin Portal on or off. At least one widget is required when enabled = true. |
style_id | Style for the portal. An empty value uses the default style. |
widgets | Ordered list of widgets. Each entry needs type and widget_id. |
type is the type of the widget, which is one of user-management-widget, role-management-widget, access-key-management-widget, user-access-key-management-widget, audit-management-widget, user-profile-widget, applications-portal-widget, tenant-profile-widget, or outbound-applications-widget. widget_id is the ID of the widget instance you want to host. You can create those widgets under Widgets and Admin Widgets, then manage them with descope_widget.
Full admin portal schema reference
If Descope is the IdP for other apps (outbound SAML/OIDC), you configure those with descope_oidc_app and descope_saml_app. See Applications and Federated Apps. Federated apps are separate from tenant SSO, where customers' IdPs sign users into your app.
Flows and Styles
If you've designed custom flows in the Descope console, you can export them and load them with Terraform:
- In the Descope console, go to Authentication Flows.
- Open the flow you want to manage, click the export button, and save the JSON file (for example,
flows/sign-up-or-in.json). - Optionally, export your flow styles from the same screen and save them as
flows/styles.json. - Reference the files in your configuration.
Each flow is a descope_flow resource keyed by its flow_id, and the project theme is a single descope_styles resource:
resource "descope_flow" "sign_up_or_in" {
project_id = descope_project.myproject.id
flow_id = "sign-up-or-in"
data = file("${path.module}/flows/sign-up-or-in.json")
}
resource "descope_styles" "main" {
project_id = descope_project.myproject.id
data = file("${path.module}/flows/styles.json")
}Full flow schema reference · Full styles schema reference
Widgets
You can build widgets, such as a user management widget or a role management widget, under Widgets in the Descope console. You can then export a widget and manage it as code the same way as flows:
- In the Descope console, go to Widgets and open the widget you want to manage.
- Export its JSON and save it (for example,
widgets/user-management.json). - Reference the file in a
descope_widgetresource with a machine-readablewidget_id.
resource "descope_widget" "user_management" {
project_id = descope_project.myproject.id
widget_id = "user-management-widget"
data = file("${path.module}/widgets/user-management.json")
}Full Terraform Plan Example
The configuration below creates a project and manages its session settings, password policy, SSO settings, a role, an HTTP connector, a flow, and an inbound app together. Every resource points at the same project through project_id, and the session settings reference the JWT template by ID.
terraform {
required_providers {
descope = {
source = "descope/descope"
version = "~> 1.0"
}
}
}
provider "descope" {}
variable "http_connector_base_url" {
type = string
}
variable "http_connector_secret" {
type = string
sensitive = true
}
resource "descope_project" "my_project" {
name = "my-project"
environment = "production"
tags = ["production", "team-auth"]
}
resource "descope_project_settings" "main" {
project_id = descope_project.my_project.id
app_url = "https://app.example.com"
approved_domains = ["example.com", "app.example.com"]
}
resource "descope_jwt_template" "app_claims" {
project_id = descope_project.my_project.id
name = "app-claims"
type = "user"
template = jsonencode({
tier = "@user.customAttributes.subscriptionTier"
})
}
resource "descope_session_settings" "main" {
project_id = descope_project.my_project.id
refresh_token_expiration = "3 weeks"
enable_inactivity = true
inactivity_time = "2 hours"
user_jwt_template = descope_jwt_template.app_claims.id
}
resource "descope_password_settings" "main" {
project_id = descope_project.my_project.id
lock = true
lock_attempts = 3
min_length = 8
disallow_email_match = true
disallowed_characters = "'\""
}
resource "descope_sso_settings" "main" {
project_id = descope_project.my_project.id
merge_users = true
redirect_url = "https://example.com/auth/callback"
require_sso_domains = true
mandatory_user_attributes = [
{ id = "email" },
{ id = "name" },
]
sso_suite_settings = {
hide_scim = false
show_help_contact = true
support_email = "it-support@example.com"
}
}
resource "descope_permission" "read_data" {
project_id = descope_project.my_project.id
name = "read:data"
description = "Read access to application data"
}
resource "descope_role" "viewer" {
project_id = descope_project.my_project.id
name = "viewer"
description = "Can read data"
permissions = [descope_permission.read_data.name]
}
resource "descope_http_connector" "eligibility" {
project_id = descope_project.my_project.id
name = "User Eligibility Check"
description = "Checks if a new user is allowed to register"
base_url = var.http_connector_base_url
authentication = {
bearer_token = var.http_connector_secret
}
}
resource "descope_flow" "sign_up_or_in" {
project_id = descope_project.my_project.id
flow_id = "sign-up-or-in"
data = file("${path.module}/flows/sign-up-or-in.json")
}
resource "descope_inbound_app" "partner" {
project_id = descope_project.my_project.id
name = "Partner Integration"
description = "OAuth client for a partner integration"
client_type = "confidential"
approved_callback_urls = ["https://partner.example.com/callback"]
permissions_scopes = [
{
name = "read:data"
description = "Read application data on the user's behalf"
values = [descope_permission.read_data.name]
}
]
}Additional Resources
Users and tenants are generally not managed via Terraform, but some dynamic resources have dedicated resource types. Defining them as code keeps access control auditable and consistent across environments.
Management Keys
You can use descope_management_key to manage Descope Management Keys as code, alongside the rest of your project configuration.
Important
The raw key value (cleartext) is only available immediately after creation and cannot be retrieved later. Store it in a secrets manager (for example, AWS Secrets Manager or HashiCorp Vault) right after terraform apply.
Keys can be scoped to restrict access at the company level, per project, or by project tag. The first example creates a company-level key and outputs its value as a sensitive output:
# Company-level key (access to all projects)
resource "descope_management_key" "pipeline_key" {
name = "CI/CD Pipeline Key"
description = "Used by the deployment pipeline to manage users"
rebac = {
company_roles = ["<role-machine-name>"]
}
}
output "pipeline_key_value" {
value = descope_management_key.pipeline_key.cleartext
sensitive = true
}Important
Role names must use the machine name format (kebab-case), not the human-readable format from the Descope Console. For example, use company-asset-mgmt-read-write instead of "Asset Management - Read & Write". See the Management Key Roles documentation for the complete list of machine names.
The second example scopes a key to a single project:
# Project-scoped key
resource "descope_management_key" "staging_key" {
name = "Staging Key"
rebac = {
project_roles = [
{
project_ids = ["__ProjectID__"]
roles = ["<role-machine-name>"]
}
]
}
}Full management key schema reference
Access Keys
You can use descope_access_key to manage project-level Access Keys as code, for machine-to-machine authentication.
Important
The cleartext value (the plaintext access key) is only available immediately after creation and cannot be retrieved later. Store it in a secrets manager (for example, AWS Secrets Manager or HashiCorp Vault) right after terraform apply.
The example below creates an access key limited to an IP range, with an expiration time:
resource "descope_access_key" "ci_pipeline" {
project_id = descope_project.myproject.id
name = "CI/CD Pipeline Key"
description = "Used by the deployment pipeline"
roles = ["Viewer"]
permitted_ips = ["10.0.0.0/8"]
expire_time = 1924991999
}
output "ci_pipeline_key" {
value = descope_access_key.ci_pipeline.cleartext
sensitive = true
}Full access key schema reference
Descopers (Console Users)
You can use descope_descoper to manage Descopers as code. Roles can be scoped to the entire company, to specific projects, or to all projects with a given tag. The available roles are admin, developer, support, and auditor.
The examples below create a company admin, a developer scoped to two projects, and a support user for every production-tagged project:
# Company admin
resource "descope_descoper" "admin" {
email = "admin@example.com"
name = "Alice Admin"
rbac = {
is_company_admin = true
}
}
# Developer scoped to specific projects
resource "descope_descoper" "developer" {
email = "dev@example.com"
name = "Bob Dev"
rbac = {
project_roles = [
{
role = "developer"
project_ids = ["P123abc", "P456def"]
}
]
}
}
# Support access for all production-tagged projects
resource "descope_descoper" "support" {
email = "support@example.com"
rbac = {
tag_roles = [
{
role = "support"
tags = ["production"]
}
]
}
}Full descoper schema reference
Applications
Like users and tenants, applications are dynamic resources that vary per environment. The Terraform provider supports Federated Apps, where Descope is the IdP for an OIDC, SAML, or WS-Fed application, and Inbound Apps, which are third-party OAuth clients with scopes and consent. Each app type is a standalone resource.
Federated Apps
You can use descope_oidc_app, descope_saml_app, and descope_wsfed_app to configure applications for outbound SSO integrations. App-scoped roles and permissions use descope_app_role and descope_app_permission.
resource "descope_oidc_app" "web" {
project_id = descope_project.myproject.id
name = "My Web App"
description = "Primary OIDC application"
force_authentication = false
claims = ["sub", "exp", "email"]
backchannel_logout_url = "https://example.com/backchannel-logout"
}
resource "descope_saml_app" "enterprise" {
project_id = descope_project.myproject.id
name = "My SAML App"
description = "Enterprise SAML integration"
force_authentication = false
default_signature_algorithm = "sha256"
manual_configuration = {
acs_url = "https://example.com/saml/acs"
entity_id = "https://example.com"
}
}Federated apps have deletion_protection enabled by default, because destroying or replacing one breaks the service provider's integration.
Full OIDC app schema reference · Full SAML app schema reference · Full WS-Fed app schema reference
Inbound Apps
You can use descope_inbound_app to manage Inbound Apps, which are third-party applications that authenticate users with Descope as an OAuth 2.0 identity provider. OAuth clients, MCP server configurations, and partner integrations all benefit from being version-controlled alongside your project.
resource "descope_inbound_app" "my_oauth_client" {
project_id = descope_project.myproject.id
name = "My OAuth Client"
description = "OAuth client application"
logo_url = "https://example.com/logo.png"
login_page_url = "__BaseURL__"
client_type = "confidential"
approved_callback_urls = [
"https://myapp.com/callback",
"http://localhost:3000/callback"
]
permissions_scopes = [
{
name = "read:profile"
description = "Read the user's profile information"
values = ["read:data"]
},
{
name = "write:profile"
description = "Modify the user's profile information"
optional = true
values = ["write:data"]
}
]
attributes_scopes = [
{
name = "email"
description = "The user's email address"
values = ["email"]
}
]
}Scopes (permissions_scopes, attributes_scopes, connections_scopes) each take the same shape:
| Field | Required | Description |
|---|---|---|
name | Yes | Unique identifier for the scope |
description | Yes | Description shown during the consent flow |
values | No | Identifiers of the underlying permissions, attributes, or connections this scope grants |
optional | No | When true, the user may decline to grant this scope during authorization |
The resource also supports force_dpop, force_pkce, default_audience, audience_whitelist, allowed_tenants, scope_claim_mapping, and a session_settings block that overrides the project's token lifetimes for this app.
To control how the app gets tokens, you can turn off individual grant types with the *_disabled attributes. You can also set client_auth_method to authenticate the app with client_assertions (private key JWT) or workload_client_assertions (workload identity) instead of a client secret.
Full inbound app schema reference
Inbound App Client Type Changes
Moving an app from the legacy empty client_type to confidential updates it in place and keeps its client ID. Any change to or from public, and any move back to the legacy value, replaces the app. The replacement gets a new client ID and client secret, so policies and clients that reference the old client ID stop matching.
The deletion_protection attribute defaults to true on inbound apps and blocks a replacement until you set it to false. If you leave client_type out, Terraform keeps the app's current type, and a new app gets your account's default client type.
Descope Engine
You can use descope_engine to register a Descope Engine as code. The Engine lets Connectors reach resources inside your private network, but it runs as a container you deploy yourself. The Terraform resource only manages the Engine's registration with Descope (its id and secret), not the container.
resource "descope_engine" "private_network" {
project_id = descope_project.myproject.id
name = "Private Network Engine"
}Using Terraform Within Your Environment
Terraform tracks your Descope project in a state file. You can store it somewhere your team can access, and remote backends like S3 or Terraform Cloud work well.
- Run
terraform planto preview changes before applying. - Run
terraform applyto apply them.
For anything Terraform doesn't cover, you can use the Management API or the management SDKs. If you manage a project with provider v0.3, the upgrading from v0.3 guide walks through moving to the standalone resources.
