Zscaler Internet Access (ZIA) Skill
Diagnose-first guidance for end users writing Terraform HCL that consumes the zscaler/zia provider. This skill does not cover provider Go code (Plugin SDK schema, expand/flatten, acceptance tests).
Canonical source of truth for resource/data-source schemas: https://registry.terraform.io/providers/zscaler/zia/latest/docs.
Response Contract
Every ZIA HCL response must include:
- Assumptions & version floor —
zscaler/zia provider version (~> 4.0 minimum for OneAPI), Terraform/OpenTofu version, auth mode (ASK if not stated — provider supports both OneAPI and legacy v3 as first-class options), cloud target (only set if non-default), and whether the configuration creates or modifies any resource (which makes activation mandatory).
- Risk category addressed — one or more of: auth misconfiguration, resource catalog mismatch (including data-source-only objects), rule ordering, predefined-rule mishandling, activation forgotten, drift, secret exposure.
- Chosen approach & tradeoffs.
- Validation plan —
terraform fmt -check, terraform validate, terraform plan -out=tfplan, plus a zia_activation_status step if any resource is created/modified (changes are draft until activated).
- Rollback notes — never
terraform state rm a ZIA resource; for predefined rules use terraform apply -target= (see Troubleshooting).
Never recommend terraform apply against a production ZIA tenant without a reviewed plan artifact and a clear activation step.
Workflow
- Capture context (see fields below).
- Diagnose intent using the routing table.
- Load only the matching reference file(s).
- Propose HCL grounded in
references/resource-catalog.md — never invent attribute names.
- Check rule ordering (if any rule resource is touched).
- Decide activation — explicit
zia_activation_status resource, or document a manual activation step.
- Validate with the commands tailored to risk tier.
- Emit the Response Contract.
Capture Context — Fields to Confirm
| Field |
Why it matters |
Default if missing |
| Provider version |
Resource catalog and rule-order validation differ between v3 (legacy) and v4+ (OneAPI). ~> 4.0 minimum. |
Assume ~> 4.0 and state it. |
| Auth mode |
OneAPI and legacy v3 are both first-class. Tenant must be migrated to Zidentity for OneAPI; otherwise legacy is the only option. FedRAMP clouds work on either path — OneAPI from v4.7.25. |
Ask. Do not default. State both options if unclear. |
| Cloud target |
OneAPI: zscaler_cloud is optional for commercial production; set gov / govus for FedRAMP, beta for non-prod. Legacy: zia_cloud is required and names the cloud (zscaler, zscloud, zscalergov, …). |
Omit zscaler_cloud for commercial production OneAPI. Ask for legacy. |
| Activation |
ANY create/update/delete on a ZIA resource needs zia_activation_status. Pure data-source workflows do not. |
If any resource is touched: include activation. Always. |
| Rule ordering |
Order is enforced server-side and must be >= 1 and contiguous. |
Ask if rule order matters. |
| Terraform runtime |
Affects optional(), moved, import, removed availability. |
Assume terraform ~> 1.9. |
Diagnose Before You Generate
| Failure category |
Symptoms |
Primary references |
| Auth misconfiguration |
401 unauthorized, vanity_domain not found, Cloud zscalergov not supported for OneAPI |
Auth & Providers |
| Resource catalog mismatch |
"Does ZIA have a resource for X?", invented attribute names, wrong block structure, plural vs singular resource name |
Resource Catalog |
| Rule ordering / predefined |
order = 0 rejected, predefined-rule destroy fails, rules drift after a delete, Request body is invalid on PUT |
Rules & Ordering |
| Activation forgotten |
terraform apply succeeds but policy doesn't change in the ZIA console |
Activation |
| Drift on every plan |
Bool keeps flipping, idleTimeInMinutes reverts to 0, predefined fields churn |
Troubleshooting: Drift Causes |
| DUPLICATE_ITEM |
400 DUPLICATE_ITEM on create — name collision with predefined or pre-existing object |
Troubleshooting: DUPLICATE_ITEM |
| Country / locale validation |
'AUC' is not a valid ISO-3166 Alpha-2 country code, country rejected on zia_location_management |
Troubleshooting: Country Code Validation |
| Secret exposure |
Credentials in .tfvars, in state, in CI logs |
Auth & Providers: Credential Hygiene |
| Cross-product question |
Prompt mentions ZPA / ZTC / ZCC alongside ZIA, asks "what's the ZPA equivalent of …", or forwards ZIA traffic to a ZPA gateway |
best-practices-skill → Cross-Product Equivalents |
Provider Block — Pick One
The provider supports two auth paths. Pick based on whether the tenant has been migrated to Zidentity. Do not default — confirm with the user.
Authoring rule (do not summarise): when emitting an OneAPI provider block, reproduce the env-var comment list verbatim — including ZSCALER_CLOUD with its OPTIONAL annotation. Users need to discover that ZSCALER_CLOUD exists as a supported (but optional) parameter; condensing it to # ZSCALER_CLIENT_ID, ZSCALER_CLIENT_SECRET, ZSCALER_VANITY_DOMAIN hides that fact.
OneAPI (Zidentity tenants)
terraform {
required_version = "~> 1.9"
required_providers {
zia = {
source = "zscaler/zia"
version = "~> 4.0"
}
}
}
provider "zia" {
# In CI, set these env vars instead of hardcoding. The first three are required,
# the fourth is optional and only used to target a non-production Zidentity environment.
# ZSCALER_CLIENT_ID (required)
# ZSCALER_CLIENT_SECRET (required; or ZSCALER_PRIVATE_KEY)
# ZSCALER_VANITY_DOMAIN (required)
# ZSCALER_CLOUD (optional — "beta" for non-prod, "gov"/"govus" for FedRAMP on v4.7.25+)
}
Legacy v3 (pre-Zidentity tenants, or FedRAMP below v4.7.25)
provider "zia" {
use_legacy_client = true
# Env vars:
# ZIA_USERNAME, ZIA_PASSWORD, ZIA_API_KEY
# ZIA_CLOUD ← REQUIRED on legacy: zscaler | zscloud | zscalerbeta | zscalerone | zscalertwo | zscalerthree | zscalergov | zscalerten | zspreview
# ZSCALER_USE_LEGACY_CLIENT=true
}
❌ Do not set zscaler_cloud = "zscaler" on OneAPI — zscaler is a legacy cloud name. On OneAPI, omit zscaler_cloud entirely for commercial production tenants. The same applies to zscalergov: on OneAPI the FedRAMP values are gov and govus.
For private-key auth, full env-var matrix, and credential hygiene, see Auth & Providers.
Resource Hierarchy
| Layer |
Purpose |
Example resources |
| Locations / forwarding |
Where traffic enters ZIA |
zia_location_management, zia_traffic_forwarding_gre_tunnel, zia_traffic_forwarding_vpn_credentials |
| Identity sources |
Users, groups, departments |
zia_user_management, zia_group_management, zia_department_management, zia_admin_user |
| URL filtering |
What categories users can access |
zia_url_filtering_rules, zia_url_categories |
| Firewall |
Network-layer policy |
zia_firewall_filtering_rule, zia_firewall_dns_rules, zia_firewall_ips_rules, zia_nat_control_rules |
| SSL / inspection |
TLS inspection rules |
zia_ssl_inspection_rules |
| DLP |
Data Loss Prevention |
zia_dlp_dictionary, zia_dlp_engines, zia_dlp_web_rules, zia_dlp_notification_templates |
| Cloud app control |
Policy on SaaS apps (M365, GDrive, …) |
zia_cloud_app_control_rule (uses zia_cloud_app_control_rule_actions data source) |
| Sandbox |
Malware sandbox policy |
zia_sandbox_rules, zia_sandbox_behavioral_analysis |
| Forwarding control |
ZIA → ZPA gateway, proxy, etc. |
zia_forwarding_control_policy, zia_forwarding_control_proxies, zia_forwarding_control_zpa_gateway |
| Activation |
Required to push draft changes live |
zia_activation_status |
Naming Conventions
- Use descriptive names:
resource "zia_url_filtering_rules" "block_gambling_for_sales", not ... "this".
- Reserve
"this" for genuine singletons (e.g. zia_activation_status).
- Prefix variables with context:
zia_dlp_engine_id, not id.
- Standard files:
main.tf, variables.tf, outputs.tf, versions.tf, providers.tf.
- ZIA rule resources end in
s (_rules, _rule) — be careful: zia_url_filtering_rules (plural) but zia_firewall_filtering_rule (singular). Always cross-check against Resource Catalog.
Rule Resources — Critical Rules
These apply to every rule-style resource: zia_url_filtering_rules, zia_firewall_filtering_rule, zia_firewall_dns_rules, zia_firewall_ips_rules, zia_dlp_web_rules, zia_ssl_inspection_rules, zia_cloud_app_control_rule, zia_forwarding_control_rule, zia_nat_control_rules, zia_sandbox_rules, zia_bandwidth_control_rules, zia_traffic_capture_rules, zia_file_type_control_rules, zia_casb_dlp_rules, zia_casb_malware_rules.
- ❌
order = 0 or negative — rejected at plan time (validation.IntAtLeast(1)); previously could create an undeletable rule.
- ❌ Non-contiguous orders (
1, 2, 5) after a delete — causes drift on next plan.
- ❌
terraform destroy against a predefined rule — not supported.
- ✅ Use
terraform apply -target=<resource> to delete specific custom rules, then re-adjust the surviving order numbers in HCL to stay contiguous.
- ✅ Predefined rules can be reordered via Terraform; changes go through cleanly as of provider v4.7.9.
Full mechanics in Rules & Ordering.
Activation — Hard Rule
Every create/update/delete on a ZIA resource produces a draft change that must be activated to take effect. This includes "metadata-only" objects like zia_rule_labels, zia_url_categories, zia_dlp_dictionary, locations, departments, and admin users — there is no resource type in ZIA that bypasses activation. Only pure data-source workflows (read-only) skip it.
Activation is tenant-wide, and queues. One call publishes every pending change in the tenant, so no configuration needs more than one activation call per run. It also does not publish immediately while any other administrator or API session holds unactivated changes — the call is queued until they activate, and cannot be cancelled.
One apply at a time per tenant. Every write acquires a single tenant-wide lock, so several states applying concurrently contend for it and produce EDIT_LOCK_NOT_AVAILABLE. Splitting state does not split the tenant: serialise apply (keyed on the tenant), keep plan parallel, and activate once after the last apply. See Activation.
| Pattern |
When |
Out-of-band ziaActivator after apply |
Recommended. Any pipeline, and any configuration built from modules. One call, timing under explicit control. |
Manage zia_activation_status in TF |
A single flat state that owns all its resources. depends_on must list every resource. |
ziaActivator once, after the last apply |
Several states against one tenant. Never one activation per state — it queues behind every other session. |
| Manual activation in console |
Acceptable for ad-hoc / emergency changes; document the step in the PR. |
❌ ZIA_ACTIVATION=true |
Avoid. Activates per resource change against a 10/min, 40/hr endpoint. Legacy; may be removed. |
| Skip activation entirely |
Only when the configuration uses data "zia_…" exclusively — no resource "zia_…". |
Recommended pattern — activation as a pipeline step:
terraform apply && ziaActivator
In-HCL alternative, valid for flat configurations:
resource "zia_url_filtering_rules" "block_gambling" {
name = "Block Gambling"
state = "ENABLED"
action = "BLOCK"
order = 1
url_categories = ["GAMBLING"]
protocols = ["ANY_RULE"]
request_methods = ["CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "OTHER", "POST", "PUT", "TRACE"]
}
resource "zia_activation_status" "this" {
status = "ACTIVE"
depends_on = [zia_url_filtering_rules.block_gambling]
}
~> Long runs: ZIA activates pending changes when a session ends, including on the API session timeout (5–20 min, default 5). An apply longer than the timeout activates part-way through and then re-authenticates — no error, but activation happened unbidden. Raise it to 20, in the Admin Portal under Administration > Advanced Settings → API Session Timeout Duration or via api_session_timeout on zia_advanced_settings, and keep runs short. ZTC has no adjustable equivalent — short runs are the only mitigation there.
See Activation for the full pattern, multi-resource batching, session-timeout details, and CI/CD wiring.
Data-Source-Only Objects
ZIA exposes 101 data sources but only 71 resources. Many objects are read-only from Terraform's perspective — they're populated by the ZIA console, the agent, your IdP, or other Zscaler products — and trying to declare them as resource blocks will fail (no such resource type exists). Common data-source-only objects:
| Category |
Data sources only (representative — not exhaustive) |
| Identity (read-only) |
zia_department_management, zia_group_management, zia_devices, zia_device_groups (all populated by the IdP / agent enrollment) |
| Location helpers |
zia_location_lite, zia_location_groups |
| Predefined catalog |
zia_dlp_dictionary_predefined_identifiers, zia_firewall_filtering_application_services, zia_firewall_filtering_network_application, zia_file_type_categories, zia_firewall_filtering_time_window |
| Tenant / cloud info |
zia_datacenters, zia_dedicated_ip_proxy, zia_cloud_applications |
| DLP infrastructure |
zia_dlp_idm_profiles, zia_dlp_idm_profile_lite, zia_dlp_edm_schema, zia_dlp_icap_servers, zia_dlp_incident_receiver_servers, zia_dlp_cloud_to_cloud_ir |
| CASB metadata |
zia_casb_email_label, zia_casb_tenant, zia_casb_tombstone_template |
| Sandbox / runtime |
zia_sandbox_report (per-MD5 lookup, not a resource) |
| Misc |
zia_domain_profiles, zia_gre_internal_ip_range_list, zia_cloud_app_control_rule_actions, zia_cloud_browser_isolation_profile, zia_forwarding_control_proxy_gateway |
Always check the official Terraform Registry before assuming there's a matching resource:
- Resources index: https://registry.terraform.io/providers/zscaler/zia/latest/docs
- Specific resource:
https://registry.terraform.io/providers/zscaler/zia/latest/docs/resources/<name_without_zia_prefix>
- Specific data source:
https://registry.terraform.io/providers/zscaler/zia/latest/docs/data-sources/<name_without_zia_prefix>
If only the data-source page exists for an object, it's read-only.
❌ Do not propose a resource "zia_department_management". Departments come from the IdP. ✅ Use data "zia_department_management" { name = "Sales" } to look one up.
Credential Hygiene
- ❌ Never put
client_secret, private_key, password, or api_key in .tfvars checked into git.
- ❌ Never echo credentials in CI job logs.
- ❌ Do not mix
ZSCALER_* and ZIA_* env vars in the same job — the provider picks one path based on use_legacy_client and silently ignores the other namespace.
- ✅ Use env vars matching your auth path:
- OneAPI:
ZSCALER_CLIENT_ID, ZSCALER_CLIENT_SECRET (or ZSCALER_PRIVATE_KEY), ZSCALER_VANITY_DOMAIN, optionally ZSCALER_CLOUD for non-prod.
- Legacy:
ZIA_USERNAME, ZIA_PASSWORD, ZIA_API_KEY, ZIA_CLOUD, ZSCALER_USE_LEGACY_CLIENT=true.
- ✅ Mark every HCL variable carrying a credential
sensitive = true.
State considerations: the OneAPI client secret is not persisted to state. IDs and configuration are. Encrypt state at rest (S3+KMS, Terraform Cloud / Enterprise) and restrict access.
Reference Files
Progressive disclosure — essentials here, depth on demand:
- Auth & Providers — OneAPI vs legacy, env vars, FedRAMP clouds (
gov / govus), multi-tenant aliases, credential hygiene.
- Resource Catalog — minimum-viable HCL for the most-used
zia_* resources, composition recipes, data-source lookups.
- Rules & Ordering —
order rules, predefined vs custom, contiguous ordering, common 400 errors, per-rule-type field stripping.
- Activation —
zia_activation_status, atomic vs manual, CI/CD pattern, gotchas.
- Troubleshooting — drift causes, DUPLICATE_ITEM, predefined rule errors, country code / DLP-name validation, debug logging, never-
state rm.
- Recent Provider Changes — auto-mined from the upstream provider CHANGELOG; lists user-facing additions and breaking changes from the last several releases.
Cross-cutting engineering discipline (state organization, CI/CD with the activation step, secret handling, testing strategy, modules, naming, versioning) lives in the sibling best-practices-skill — load it whenever the question is about how to structure or operate a Zscaler-Terraform repo rather than how to call a specific zia_* resource.
Authoring Rule — Grounding for Uncatalogued Resources
The reference catalog ships canonical HCL for the most-used zia_* resources, but ZIA exposes 71 resources and 101 data sources — too many to inline. When asked about a resource or data source not in Resource Catalog:
- Fetch the official Registry page first before generating any HCL:
- Resource:
https://registry.terraform.io/providers/zscaler/zia/latest/docs/resources/<name_without_zia_prefix>
- Data source:
https://registry.terraform.io/providers/zscaler/zia/latest/docs/data-sources/<name_without_zia_prefix>
- Ground every attribute name in that fetched page.
- State the Registry URL you used in the Response Contract
Assumptions section.
❌ Never invent attribute names because "they look like other ZIA resources." ✅ If the Registry page does not exist for a zia_<name>, the resource does not exist — say so explicitly and ask whether the user means a related object.
What This Skill Will Not Do
- Generate HCL with attribute names not documented on the official Terraform Registry page for that resource.
- Recommend
terraform state rm for ZIA resources.
- Cover provider development (Plugin SDK schema, expand/flatten, acceptance tests, sweepers) — out of scope.
1---2name: zia-skill3description: Use when writing, reviewing, or debugging Terraform HCL that uses the Zscaler Internet Access (ZIA) provider — covers provider auth (OneAPI / legacy / multi-cloud), the resource catalog (URL filtering, firewall, DLP, SSL inspection, cloud app control, sandbox), rule ordering semantics (predefined vs custom rules, contiguous order requirement, IntAtLeast(1)), the activation lifecycle, and known API quirks (DUPLICATE_ITEM, predefined-rule reorder, country-code validation).4license: MIT5---67# Zscaler Internet Access (ZIA) Skill89Diagnose-first guidance for **end users writing Terraform HCL that consumes the `zscaler/zia` provider**. This skill does not cover provider Go code (Plugin SDK schema, expand/flatten, acceptance tests).1011**Canonical source of truth** for resource/data-source schemas: <https://registry.terraform.io/providers/zscaler/zia/latest/docs>.1213## Response Contract1415Every ZIA HCL response must include:16171. **Assumptions & version floor** — `zscaler/zia` provider version (`~> 4.0` minimum for OneAPI), Terraform/OpenTofu version, **auth mode (ASK if not stated — provider supports both OneAPI and legacy v3 as first-class options)**, cloud target (only set if non-default), and **whether the configuration creates or modifies any resource** (which makes activation mandatory).182. **Risk category addressed** — one or more of: auth misconfiguration, resource catalog mismatch (including data-source-only objects), rule ordering, predefined-rule mishandling, activation forgotten, drift, secret exposure.193. **Chosen approach & tradeoffs.**204. **Validation plan** — `terraform fmt -check`, `terraform validate`, `terraform plan -out=tfplan`, **plus a `zia_activation_status` step if any resource is created/modified** (changes are draft until activated).215. **Rollback notes** — never `terraform state rm` a ZIA resource; for predefined rules use `terraform apply -target=` (see [Troubleshooting](references/troubleshooting.md#never-state-rm-a-zia-resource)).2223Never recommend `terraform apply` against a production ZIA tenant without a reviewed plan artifact and a clear activation step.2425## Workflow26271. **Capture context** (see fields below).282. **Diagnose intent** using the routing table.293. **Load only the matching reference file(s).**304. **Propose HCL** grounded in `references/resource-catalog.md` — never invent attribute names.315. **Check rule ordering** (if any rule resource is touched).326. **Decide activation** — explicit `zia_activation_status` resource, or document a manual activation step.337. **Validate** with the commands tailored to risk tier.348. **Emit the Response Contract.**3536## Capture Context — Fields to Confirm3738| Field | Why it matters | Default if missing |39| ----------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------- |40| Provider version | Resource catalog and rule-order validation differ between v3 (legacy) and v4+ (OneAPI). `~> 4.0` minimum. | Assume `~> 4.0` and state it. |41| **Auth mode** | **OneAPI and legacy v3 are both first-class.** Tenant must be migrated to Zidentity for OneAPI; otherwise legacy is the only option. FedRAMP clouds work on **either** path — OneAPI from `v4.7.25`. | **Ask. Do not default.** State both options if unclear. |42| Cloud target | OneAPI: `zscaler_cloud` is **optional** for commercial production; set `gov` / `govus` for FedRAMP, `beta` for non-prod. Legacy: `zia_cloud` is required and names the cloud (`zscaler`, `zscloud`, `zscalergov`, …). | **Omit `zscaler_cloud` for commercial production OneAPI.** Ask for legacy. |43| Activation | **ANY** create/update/delete on a ZIA resource needs `zia_activation_status`. Pure data-source workflows do not. | If any resource is touched: include activation. Always. |44| Rule ordering | Order is enforced server-side and must be `>= 1` and contiguous. | Ask if rule order matters. |45| Terraform runtime | Affects `optional()`, `moved`, `import`, `removed` availability. | Assume `terraform ~> 1.9`. |4647## Diagnose Before You Generate4849| Failure category | Symptoms | Primary references |50| ------------------------------ | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |51| **Auth misconfiguration** | `401 unauthorized`, `vanity_domain not found`, `Cloud zscalergov not supported for OneAPI` | [Auth & Providers](references/auth-and-providers.md) |52| **Resource catalog mismatch** | "Does ZIA have a resource for X?", invented attribute names, wrong block structure, plural vs singular resource name | [Resource Catalog](references/resource-catalog.md) |53| **Rule ordering / predefined** | `order = 0` rejected, predefined-rule destroy fails, rules drift after a delete, `Request body is invalid` on PUT | [Rules & Ordering](references/rules-and-ordering.md) |54| **Activation forgotten** | `terraform apply` succeeds but policy doesn't change in the ZIA console | [Activation](references/activation.md) |55| **Drift on every plan** | Bool keeps flipping, `idleTimeInMinutes` reverts to 0, predefined fields churn | [Troubleshooting: Drift Causes](references/troubleshooting.md#drift-causes) |56| **DUPLICATE_ITEM** | `400 DUPLICATE_ITEM` on create — name collision with predefined or pre-existing object | [Troubleshooting: DUPLICATE_ITEM](references/troubleshooting.md#duplicate_item-on-create) |57| **Country / locale validation**| `'AUC' is not a valid ISO-3166 Alpha-2 country code`, `country` rejected on `zia_location_management` | [Troubleshooting: Country Code Validation](references/troubleshooting.md#country-code--locale-validation) |58| **Secret exposure** | Credentials in `.tfvars`, in state, in CI logs | [Auth & Providers: Credential Hygiene](references/auth-and-providers.md#credential-hygiene) |59| **Cross-product question** | Prompt mentions ZPA / ZTC / ZCC alongside ZIA, asks "what's the ZPA equivalent of …", or forwards ZIA traffic to a ZPA gateway | `best-practices-skill` → [Cross-Product Equivalents](../best-practices-skill/references/cross-product-equivalents.md) |6061## Provider Block — Pick One6263The provider supports **two** auth paths. Pick based on whether the tenant has been migrated to Zidentity. **Do not default** — confirm with the user.6465> **Authoring rule (do not summarise):** when emitting an OneAPI provider block, reproduce the env-var comment list **verbatim** — including `ZSCALER_CLOUD` with its `OPTIONAL` annotation. Users need to discover that `ZSCALER_CLOUD` exists as a supported (but optional) parameter; condensing it to `# ZSCALER_CLIENT_ID, ZSCALER_CLIENT_SECRET, ZSCALER_VANITY_DOMAIN` hides that fact.6667### OneAPI (Zidentity tenants)6869```hcl70terraform {71 required_version = "~> 1.9"72 required_providers {73 zia = {74 source = "zscaler/zia"75 version = "~> 4.0"76 }77 }78}7980provider "zia" {81 # In CI, set these env vars instead of hardcoding. The first three are required,82 # the fourth is optional and only used to target a non-production Zidentity environment.83 # ZSCALER_CLIENT_ID (required)84 # ZSCALER_CLIENT_SECRET (required; or ZSCALER_PRIVATE_KEY)85 # ZSCALER_VANITY_DOMAIN (required)86 # ZSCALER_CLOUD (optional — "beta" for non-prod, "gov"/"govus" for FedRAMP on v4.7.25+)87}88```8990### Legacy v3 (pre-Zidentity tenants, or FedRAMP below `v4.7.25`)9192```hcl93provider "zia" {94 use_legacy_client = true95 # Env vars:96 # ZIA_USERNAME, ZIA_PASSWORD, ZIA_API_KEY97 # ZIA_CLOUD ← REQUIRED on legacy: zscaler | zscloud | zscalerbeta | zscalerone | zscalertwo | zscalerthree | zscalergov | zscalerten | zspreview98 # ZSCALER_USE_LEGACY_CLIENT=true99}100```101102❌ Do not set `zscaler_cloud = "zscaler"` on OneAPI — `zscaler` is a **legacy** cloud name. On OneAPI, omit `zscaler_cloud` entirely for commercial production tenants. The same applies to `zscalergov`: on OneAPI the FedRAMP values are `gov` and `govus`.103104For private-key auth, full env-var matrix, and credential hygiene, see [Auth & Providers](references/auth-and-providers.md).105106## Resource Hierarchy107108| Layer | Purpose | Example resources |109| -------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |110| **Locations / forwarding** | Where traffic enters ZIA | `zia_location_management`, `zia_traffic_forwarding_gre_tunnel`, `zia_traffic_forwarding_vpn_credentials` |111| **Identity sources** | Users, groups, departments | `zia_user_management`, `zia_group_management`, `zia_department_management`, `zia_admin_user` |112| **URL filtering** | What categories users can access | `zia_url_filtering_rules`, `zia_url_categories` |113| **Firewall** | Network-layer policy | `zia_firewall_filtering_rule`, `zia_firewall_dns_rules`, `zia_firewall_ips_rules`, `zia_nat_control_rules` |114| **SSL / inspection** | TLS inspection rules | `zia_ssl_inspection_rules` |115| **DLP** | Data Loss Prevention | `zia_dlp_dictionary`, `zia_dlp_engines`, `zia_dlp_web_rules`, `zia_dlp_notification_templates` |116| **Cloud app control** | Policy on SaaS apps (M365, GDrive, …) | `zia_cloud_app_control_rule` (uses `zia_cloud_app_control_rule_actions` data source) |117| **Sandbox** | Malware sandbox policy | `zia_sandbox_rules`, `zia_sandbox_behavioral_analysis` |118| **Forwarding control** | ZIA → ZPA gateway, proxy, etc. | `zia_forwarding_control_policy`, `zia_forwarding_control_proxies`, `zia_forwarding_control_zpa_gateway` |119| **Activation** | **Required** to push draft changes live | `zia_activation_status` |120121## Naming Conventions122123- Use descriptive names: `resource "zia_url_filtering_rules" "block_gambling_for_sales"`, not `... "this"`.124- Reserve `"this"` for genuine singletons (e.g. `zia_activation_status`).125- Prefix variables with context: `zia_dlp_engine_id`, not `id`.126- Standard files: `main.tf`, `variables.tf`, `outputs.tf`, `versions.tf`, `providers.tf`.127- ZIA rule resources end in `s` (`_rules`, `_rule`) — be careful: `zia_url_filtering_rules` (plural) but `zia_firewall_filtering_rule` (singular). Always cross-check against [Resource Catalog](references/resource-catalog.md).128129## Rule Resources — Critical Rules130131These apply to **every** rule-style resource: `zia_url_filtering_rules`, `zia_firewall_filtering_rule`, `zia_firewall_dns_rules`, `zia_firewall_ips_rules`, `zia_dlp_web_rules`, `zia_ssl_inspection_rules`, `zia_cloud_app_control_rule`, `zia_forwarding_control_rule`, `zia_nat_control_rules`, `zia_sandbox_rules`, `zia_bandwidth_control_rules`, `zia_traffic_capture_rules`, `zia_file_type_control_rules`, `zia_casb_dlp_rules`, `zia_casb_malware_rules`.132133- ❌ `order = 0` or negative — rejected at plan time (`validation.IntAtLeast(1)`); previously could create an undeletable rule.134- ❌ Non-contiguous orders (`1, 2, 5`) after a delete — causes drift on next plan.135- ❌ `terraform destroy` against a predefined rule — not supported.136- ✅ Use `terraform apply -target=<resource>` to delete specific custom rules, then re-adjust the surviving order numbers in HCL to stay contiguous.137- ✅ Predefined rules **can** be reordered via Terraform; changes go through cleanly as of provider v4.7.9.138139Full mechanics in [Rules & Ordering](references/rules-and-ordering.md).140141## Activation — Hard Rule142143**Every** create/update/delete on a ZIA resource produces a draft change that **must** be activated to take effect. This includes "metadata-only" objects like `zia_rule_labels`, `zia_url_categories`, `zia_dlp_dictionary`, locations, departments, and admin users — there is no resource type in ZIA that bypasses activation. Only **pure data-source workflows** (read-only) skip it.144145**Activation is tenant-wide, and queues.** One call publishes every pending change in the tenant, so no configuration needs more than one activation call per run. It also does not publish immediately while any other administrator or API session holds unactivated changes — the call is queued until they activate, and cannot be cancelled.146147**One apply at a time per tenant.** Every write acquires a single tenant-wide lock, so several states applying concurrently contend for it and produce `EDIT_LOCK_NOT_AVAILABLE`. Splitting state does not split the tenant: serialise `apply` (keyed on the tenant), keep `plan` parallel, and activate once after the last apply. See [Activation](references/activation.md#several-states-one-tenant).148149| Pattern | When |150| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |151| **Out-of-band `ziaActivator` after apply** | **Recommended.** Any pipeline, and any configuration built from modules. One call, timing under explicit control. |152| Manage `zia_activation_status` in TF | A single flat state that owns all its resources. `depends_on` must list **every** resource. |153| **`ziaActivator` once, after the last apply** | **Several states against one tenant.** Never one activation per state — it queues behind every other session. |154| Manual activation in console | Acceptable for ad-hoc / emergency changes; document the step in the PR. |155| ❌ `ZIA_ACTIVATION=true` | **Avoid.** Activates per resource change against a 10/min, 40/hr endpoint. Legacy; may be removed. |156| **Skip activation entirely** | **Only** when the configuration uses `data "zia_…"` exclusively — no `resource "zia_…"`. |157158Recommended pattern — activation as a pipeline step:159160```bash161terraform apply && ziaActivator162```163164In-HCL alternative, valid for flat configurations:165166```hcl167resource "zia_url_filtering_rules" "block_gambling" {168 name = "Block Gambling"169 state = "ENABLED"170 action = "BLOCK"171 order = 1172 url_categories = ["GAMBLING"]173 protocols = ["ANY_RULE"]174 request_methods = ["CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "OTHER", "POST", "PUT", "TRACE"]175}176177resource "zia_activation_status" "this" {178 status = "ACTIVE"179 depends_on = [zia_url_filtering_rules.block_gambling]180}181```182183~> Long runs: ZIA activates pending changes **when a session ends**, including on the API session timeout (5–20 min, default 5). An apply longer than the timeout activates part-way through and then re-authenticates — no error, but activation happened unbidden. Raise it to 20, in the Admin Portal under **Administration > Advanced Settings** → *API Session Timeout Duration* or via `api_session_timeout` on `zia_advanced_settings`, and keep runs short. **ZTC has no adjustable equivalent** — short runs are the only mitigation there.184185See [Activation](references/activation.md) for the full pattern, multi-resource batching, session-timeout details, and CI/CD wiring.186187## Data-Source-Only Objects188189ZIA exposes **101 data sources but only 71 resources**. Many objects are read-only from Terraform's perspective — they're populated by the ZIA console, the agent, your IdP, or other Zscaler products — and trying to declare them as `resource` blocks will fail (no such resource type exists). Common data-source-only objects:190191| Category | Data sources only (representative — not exhaustive) |192| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |193| Identity (read-only) | `zia_department_management`, `zia_group_management`, `zia_devices`, `zia_device_groups` (all populated by the IdP / agent enrollment) |194| Location helpers | `zia_location_lite`, `zia_location_groups` |195| Predefined catalog | `zia_dlp_dictionary_predefined_identifiers`, `zia_firewall_filtering_application_services`, `zia_firewall_filtering_network_application`, `zia_file_type_categories`, `zia_firewall_filtering_time_window` |196| Tenant / cloud info | `zia_datacenters`, `zia_dedicated_ip_proxy`, `zia_cloud_applications` |197| DLP infrastructure | `zia_dlp_idm_profiles`, `zia_dlp_idm_profile_lite`, `zia_dlp_edm_schema`, `zia_dlp_icap_servers`, `zia_dlp_incident_receiver_servers`, `zia_dlp_cloud_to_cloud_ir` |198| CASB metadata | `zia_casb_email_label`, `zia_casb_tenant`, `zia_casb_tombstone_template` |199| Sandbox / runtime | `zia_sandbox_report` (per-MD5 lookup, not a resource) |200| Misc | `zia_domain_profiles`, `zia_gre_internal_ip_range_list`, `zia_cloud_app_control_rule_actions`, `zia_cloud_browser_isolation_profile`, `zia_forwarding_control_proxy_gateway` |201202Always check the official Terraform Registry before assuming there's a matching resource:203204- Resources index: <https://registry.terraform.io/providers/zscaler/zia/latest/docs>205- Specific resource: `https://registry.terraform.io/providers/zscaler/zia/latest/docs/resources/<name_without_zia_prefix>`206- Specific data source: `https://registry.terraform.io/providers/zscaler/zia/latest/docs/data-sources/<name_without_zia_prefix>`207208If only the data-source page exists for an object, it's read-only.209210❌ Do not propose a `resource "zia_department_management"`. Departments come from the IdP. ✅ Use `data "zia_department_management" { name = "Sales" }` to look one up.211212## Credential Hygiene213214- ❌ Never put `client_secret`, `private_key`, `password`, or `api_key` in `.tfvars` checked into git.215- ❌ Never echo credentials in CI job logs.216- ❌ **Do not mix `ZSCALER_*` and `ZIA_*` env vars in the same job** — the provider picks one path based on `use_legacy_client` and silently ignores the other namespace.217- ✅ Use env vars matching your auth path:218 - **OneAPI:** `ZSCALER_CLIENT_ID`, `ZSCALER_CLIENT_SECRET` (or `ZSCALER_PRIVATE_KEY`), `ZSCALER_VANITY_DOMAIN`, optionally `ZSCALER_CLOUD` for non-prod.219 - **Legacy:** `ZIA_USERNAME`, `ZIA_PASSWORD`, `ZIA_API_KEY`, `ZIA_CLOUD`, `ZSCALER_USE_LEGACY_CLIENT=true`.220- ✅ Mark every HCL variable carrying a credential `sensitive = true`.221222State considerations: the OneAPI client secret is **not** persisted to state. IDs and configuration are. Encrypt state at rest (S3+KMS, Terraform Cloud / Enterprise) and restrict access.223224## Reference Files225226Progressive disclosure — essentials here, depth on demand:227228- [Auth & Providers](references/auth-and-providers.md) — OneAPI vs legacy, env vars, FedRAMP clouds (`gov` / `govus`), multi-tenant aliases, credential hygiene.229- [Resource Catalog](references/resource-catalog.md) — minimum-viable HCL for the most-used `zia_*` resources, composition recipes, data-source lookups.230- [Rules & Ordering](references/rules-and-ordering.md) — `order` rules, predefined vs custom, contiguous ordering, common 400 errors, per-rule-type field stripping.231- [Activation](references/activation.md) — `zia_activation_status`, atomic vs manual, CI/CD pattern, gotchas.232- [Troubleshooting](references/troubleshooting.md) — drift causes, DUPLICATE_ITEM, predefined rule errors, country code / DLP-name validation, debug logging, never-`state rm`.233- [Recent Provider Changes](references/recent-provider-changes.md) — auto-mined from the upstream provider CHANGELOG; lists user-facing additions and breaking changes from the last several releases.234235**Cross-cutting engineering discipline** (state organization, CI/CD with the activation step, secret handling, testing strategy, modules, naming, versioning) lives in the sibling **`best-practices-skill`** — load it whenever the question is about how to structure or operate a Zscaler-Terraform repo rather than how to call a specific `zia_*` resource.236237## Authoring Rule — Grounding for Uncatalogued Resources238239The reference catalog ships canonical HCL for the most-used `zia_*` resources, but ZIA exposes 71 resources and 101 data sources — too many to inline. When asked about a resource or data source not in [Resource Catalog](references/resource-catalog.md):2402411. **Fetch the official Registry page first** before generating any HCL:242 - Resource: `https://registry.terraform.io/providers/zscaler/zia/latest/docs/resources/<name_without_zia_prefix>`243 - Data source: `https://registry.terraform.io/providers/zscaler/zia/latest/docs/data-sources/<name_without_zia_prefix>`2442. Ground every attribute name in that fetched page.2453. State the Registry URL you used in the Response Contract `Assumptions` section.246247❌ Never invent attribute names because "they look like other ZIA resources." ✅ If the Registry page does not exist for a `zia_<name>`, the resource does not exist — say so explicitly and ask whether the user means a related object.248249## What This Skill Will Not Do250251- Generate HCL with attribute names not documented on the official Terraform Registry page for that resource.252- Recommend `terraform state rm` for ZIA resources.253- Cover provider development (Plugin SDK schema, expand/flatten, acceptance tests, sweepers) — out of scope.