Preview rule diffs
The Rules Preview API applies Operational Rules to a module and returns the diff without serving the module. Nothing is downloaded, cached into your project, or changed in your organization's configuration. The Rules Playground is the browser UI over the public route; this page is the API behind it.
Two routes
| Public route | Private route | |
|---|---|---|
| Path | https://registry.compliance.tf/api/v1/modules/{namespace}/{name}/{provider}/{version}/rules/preview | https://registry.compliance.tf/v1/rules/preview/{namespace}/{name}/{provider}/{version} |
| Who | Anyone. No account, and a free account gets the same response | A signed-in organization member (the dashboard session, not a registry token) |
| Modules | The reviewed allowlist, at the version pinned for each | Any module in the catalog |
| Rules come from | ?rules= only, and it is required | Your organization's Baseline, plus ?add_rules=, ?ruleset= or rp64 |
| Rule ids per request | 8 | 50 |
| Budget | Cache misses per IP: 6 a minute, 60 an hour, 2 builds at once | A per-day quota by plan |
Both routes refuse ?framework=, ?enable= and ?disable= with PREVIEW_PARAM_NOT_ACCEPTED rather than ignoring them: a silently dropped parameter would leave you believing you had previewed something you had not. The public route also refuses ?add_rules= for the same reason - there is no Baseline there to add to - and answers PREVIEW_TIER_REQUIRED to ?ruleset= and rp64.
The public route serves only the pinned version of an allowlisted module. Any other version, including latest, answers PREVIEW_VERSION_NOT_PINNED. The Playground shows the pinned version for each module.
Request
# Public route: no credentials. `?rules=` is required and comma-separated.
curl "https://registry.compliance.tf/api/v1/modules/terraform-aws-modules/s3-bucket/aws/5.15.4/rules/preview?rules=lifecycle_prevent_destroy_data,lifecycle_ignore_tags"The private route takes the same module path after https://registry.compliance.tf/v1/rules/preview/ and is called from a signed-in dashboard session. It previews the module against the organization's Baseline, so an empty request shows what the Baseline alone would do, and ?add_rules= adds to it.
Response
The body has nine top-level keys. Trimmed from a real public-route response for the request above:
{
"schema": "1.0",
"module": { "namespace": "terraform-aws-modules", "name": "s3-bucket", "provider": "aws", "version": "5.15.4" },
"rules": {
"requested": ["lifecycle_prevent_destroy_data", "lifecycle_ignore_tags"],
"add_requested": [],
"resolved": ["lifecycle_ignore_tags", "lifecycle_prevent_destroy_data"],
"source": "query",
"baseline": null
},
"summary": {
"success": true,
"error": null,
"files_scanned": 4,
"files_changed": 1,
"full_text_omitted": false,
"scope_coverage": "module_root_only",
"submodules_omitted": ["modules/notification", "modules/object"]
},
"outcomes": {
"lifecycle_prevent_destroy_data": { "outcome": "enforced", "errors": [] },
"lifecycle_ignore_tags": { "outcome": "enforced", "errors": [] }
},
"files": [
{
"path": "main.tf",
"unified": "--- main.tf (original)\n+++ main.tf (after rules)\n@@ -54,6 +54,10 @@\n ...",
"before": "...",
"after": "..."
}
],
"conflicts": [],
"danger": [],
"cache": { "key_version": 4, "preview_id": "1b6a45f7..." }
}rules.resolvedis the list the engine ran, in the order it ran them. On the private routerules.baselinereports the organization's Baseline state instead ofnull.outcomeshas one entry per resolved rule id.enforcedmeans the rule changed something;not_applicablemeans nothing in the module matched; a configured-but-empty rule reportsnot_configured. Read this, not the presence of a rule in the request, to know what happened.fileshas one entry per changed file:path, aunifieddiff, and the fullbeforeandaftertext. When the module is large the full text is dropped andsummary.full_text_omittedsays so; the unified diff stays.summary.scope_coverageismodule_root_onlywhen the preview ran on the root module only;submodules_omittedlists what was not previewed. A rule whose target lives in a submodule can reportnot_applicablehere and still apply at download.conflictslists rules that could not be applied together;dangerlists transformations the engine flags as risky. Both are empty in the normal case.
A download built with the same rules writes the same outcomes into compliancetf-manifest.json. The preview is the manifest before the fact; the manifest is the record after it. See Operational Rules for how to read it.
Errors
Errors are JSON with code, error, remediation and a request_id to quote when asking for help. The ones you will meet:
| Code | Meaning |
|---|---|
PREVIEW_VERSION_NOT_PINNED | Public route: the version is not the one pinned for this module. Use the version the Playground shows. |
PREVIEW_MODULE_NOT_ALLOWLISTED | Public route: the module is not on the reviewed list. |
PREVIEW_PARAM_NOT_ACCEPTED | A posture parameter this route does not take (add_rules on the public route, framework, enable, disable on either). |
RULE_LIST_TOO_LONG | More than 8 ids on the public route, or 50 on the private one. |
RULE_UNKNOWN | An id that is not in the catalog. The definitions page lists every selectable id. |
RATE_LIMITED | The cache-miss budget (public) or the daily quota (private) is spent. The response says when it resets. |
PREVIEW_ENGINE_CATALOG_SKEW | The engine and the rule catalog disagree about a rule. Not transient: quote the request_id. |
Next steps
- Rules Playground — the same public route, with a module and rule picker and the diff rendered
- Getting Started with Operational Rules — apply rules with
?add_rules=or your organization's Baseline - Rule Catalog — every documented rule with before/after examples