Skip to main content
Issuance policies let you enforce compliance, jurisdiction, and trust requirements across minting, verification, and proof-bundle exports. When a policy is active, every matching request is evaluated against your rules before it proceeds. Requests that don’t match are denied with a clear reason.

When to use policies

Use issuance policies when you need to:
  • Restrict minting to issuers in specific jurisdictions (e.g., US-only or US and EU)
  • Require a minimum trust tier before an issuer can create attestations
  • Enforce different rules for minting vs. verification
  • Control who can export proof bundles or submit data-portability requests
  • Test a policy against sample inputs before deploying to production
  • Audit which policy was in effect when an attestation was created
If you don’t create any policies, all minting, verification, and export requests are allowed by default.

How policies work

Each policy contains one or more rules. A rule has:
  • Conditions — field-level checks evaluated against the request context (e.g., jurisdiction eq US)
  • EffectALLOW or DENY if the conditions match
  • Default effect — applied when no rule matches
Rules are evaluated in order. The first matching rule determines the outcome. If no rule matches, the default_effect applies.
{
  "rules": [
    {
      "id": "us_only",
      "description": "US jurisdiction required",
      "conditions": [
        { "field": "jurisdiction", "op": "eq", "value": "US" }
      ],
      "effect": "ALLOW"
    }
  ],
  "default_effect": "DENY"
}

Condition operators

OperatorDescriptionExample
eqEquals{ "field": "jurisdiction", "op": "eq", "value": "US" }
neqNot equals{ "field": "trust_tier", "op": "neq", "value": "individual" }
inMatches any value in a list{ "field": "trust_tier", "op": "in", "value": ["verified_org", "enterprise"] }
ninDoes not match any value in a list{ "field": "jurisdiction", "op": "nin", "value": ["CN", "RU"] }
gtGreater than (numeric){ "field": "key.age_days", "op": "gt", "value": 90 }
ltLess than (numeric){ "field": "key.age_days", "op": "lt", "value": 365 }
existsField is present{ "field": "jurisdiction", "op": "exists", "value": true }
Conditions support dot-notation for nested fields (e.g., key.age_days, key.status).

Available fields

Any field in the evaluation input can be referenced, including nested fields with dot-notation. The following fields are available by default:
FieldDescription
jurisdictionThe issuer’s registered jurisdiction (ISO country code)
trust_tierThe issuer’s trust tier (individual, verified_org, regulated_issuer, enterprise)
statusThe issuer’s current status (ACTIVE, suspended, revoked, SUNSET, pending)
risk_ratingThe issuer’s risk rating (low, medium, high, CRITICAL)
assurance_levelThe issuer’s assurance level (standard, high, regulated)
key.age_daysAge of the signing key in days
key.statusStatus of the signing key (ACTIVE, REVOKED, EXPIRED)
key.kidIdentifier of the signing key

Policy lifecycle

Each policy has a status that controls whether it is enforced:
StatusBehavior
DRAFTSaved but not enforced — use this while you iterate on rules
ACTIVEEnforced on every matching request
DISABLEDTemporarily turned off without deleting the policy
When you create a policy you choose the initial status. Only ACTIVE policies are evaluated at request time.

Lifecycle transitions

Policies typically follow this progression:
  1. Create as DRAFT — write and refine your rules without affecting live traffic.
  2. Simulate — test the draft policy against sample inputs using the policy simulator.
  3. Activate — set the status to ACTIVE so the policy is enforced on matching requests.
  4. Disable — set the status to DISABLED to temporarily stop enforcement without losing the policy definition.
  5. Delete — remove the policy entirely when it is no longer needed.
You can transition between DRAFT, ACTIVE, and DISABLED at any time by updating the policy. Each status change increments the policy version, which is recorded alongside every evaluation decision for auditability.
Transitioning a policy to ACTIVE takes effect immediately. Make sure you have tested the rules with the simulator before activating.

Policy categories

Policies are scoped to a specific action:
CategoryApplies to
MINTAttestation minting requests
VERIFYVerification requests
BUNDLE_EXPORTProof-bundle export and data-portability requests
You can have multiple active policies. Each policy applies only to its category.

Export control policies

BUNDLE_EXPORT policies control who can export proof bundles and submit data-portability requests. Use them to restrict exports by jurisdiction, trust tier, or any other field in the evaluation input.
{
  "rules": [
    {
      "id": "block_non_enterprise",
      "description": "Only enterprise-tier issuers can export bundles",
      "conditions": [
        { "field": "trust_tier", "op": "nin", "value": ["enterprise", "regulated_issuer"] }
      ],
      "effect": "DENY"
    },
    {
      "id": "allow_low_risk",
      "description": "Allow exports for low-risk issuers",
      "conditions": [
        { "field": "risk_rating", "op": "eq", "value": "low" }
      ],
      "effect": "ALLOW"
    }
  ],
  "default_effect": "DENY"
}
To create this policy via the API, set category to BUNDLE_EXPORT:
curl -X POST https://api.truthlocks.com/v1/policies \
  -H "X-API-Key: tl_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Enterprise Export Only",
    "category": "BUNDLE_EXPORT",
    "status": "ACTIVE",
    "description": "Restrict proof-bundle exports to enterprise issuers with low risk",
    "language": "json_rules",
    "rules": {
      "rules": [
        {
          "id": "block_non_enterprise",
          "description": "Only enterprise-tier issuers can export bundles",
          "conditions": [
            { "field": "trust_tier", "op": "nin", "value": ["enterprise", "regulated_issuer"] }
          ],
          "effect": "DENY"
        }
      ],
      "default_effect": "ALLOW"
    }
  }'

Policy bindings

Policy bindings connect a policy to a specific target so the engine knows when to apply it. Each binding includes:
FieldTypeDescription
policy_idstringThe ID of the policy to bind
target_typestringISSUER, VERIFICATION_PROFILE, or TENANT_DEFAULT
target_idstringUUID of the target. Omit for TENANT_DEFAULT to apply the policy to all entities.
actionstringMINT, VERIFY, or BUNDLE_EXPORT
priorityintegerHigher-priority bindings are evaluated first
When multiple bindings match a request, they are evaluated in descending priority order. The first DENY result stops evaluation immediately.

Creating a binding via the API

curl -X POST https://api.truthlocks.com/v1/policies/bindings \
  -H "X-API-Key: tl_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "policy_id": "pol_abc123",
    "target_type": "ISSUER",
    "target_id": "iss_def456",
    "action": "MINT",
    "priority": 100
  }'
To apply a policy as the tenant-wide default, set target_type to TENANT_DEFAULT and omit target_id:
curl -X POST https://api.truthlocks.com/v1/policies/bindings \
  -H "X-API-Key: tl_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "policy_id": "pol_abc123",
    "target_type": "TENANT_DEFAULT",
    "action": "MINT",
    "priority": 10
  }'
Use higher priority values for more specific bindings. For example, assign priority 100 to an issuer-level binding and 10 to the tenant default so the issuer-specific policy is evaluated first.

Creating a policy

From the console

  1. Go to Policies in the console sidebar
  2. Click Create policy
  3. Enter a name, select a category (MINT, VERIFY, or BUNDLE_EXPORT), and choose a status
  4. Write your rules in JSON format or use a template
  5. Click Deploy Policy — if the status is ACTIVE, it takes effect immediately

From a template

The console includes four ready-to-use templates you can add with one click:
TemplateCategoryDescription
Allow US JurisdictionMINTOnly allow minting from US-based issuers
Verified Org OnlyMINTRequire verified_org trust tier or higher
Allow All (Permissive)MINTAllow all mints — use with caution
Verify - US & EU OnlyVERIFYOnly accept verifications from US or EU jurisdictions
To use a template, open the Templates tab on the policies page and click Use template. The policy is created and activated immediately. You can edit it afterwards to customize the rules.

Via the API

Set status to DRAFT if you want to save the policy without enforcing it yet.
curl -X POST https://api.truthlocks.com/v1/policies \
  -H "X-API-Key: tl_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "US Issuers Only",
    "category": "MINT",
    "status": "ACTIVE",
    "description": "Restrict minting to US-based issuers",
    "language": "json_rules",
    "rules": {
      "rules": [
        {
          "id": "us_only",
          "description": "US jurisdiction required",
          "conditions": [
            { "field": "jurisdiction", "op": "eq", "value": "US" }
          ],
          "effect": "ALLOW"
        }
      ],
      "default_effect": "DENY"
    }
  }'
Response
{
  "id": "pol_abc123",
  "name": "US Issuers Only",
  "category": "MINT",
  "status": "ACTIVE",
  "description": "Restrict minting to US-based issuers",
  "rules": { "..." },
  "version": 1,
  "created_at": "2026-03-31T10:00:00Z"
}

Listing policies

curl https://api.truthlocks.com/v1/policies \
  -H "X-API-Key: tl_live_..."
Returns an array of all policies for the current tenant.

Simulating a policy

Before relying on a policy in production, use the simulator to test how it evaluates a given input. In the console, open the Simulator tab on the policies page, select a policy, and enter a test input. You can also call the evaluate endpoint directly:
curl -X POST https://api.truthlocks.com/v1/policies/evaluate \
  -H "X-API-Key: tl_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "MINT",
    "target_type": "ISSUER",
    "input": {
      "jurisdiction": "US",
      "trust_tier": "ENTERPRISE"
    }
  }'
Response
{
  "allowed": true,
  "matched_rules": ["us_only"],
  "reasons": [],
  "decision_id": "dec_7f3a1b"
}
The response includes four fields:
FieldTypeDescription
allowedbooleanWhether the request passed all active policies
matched_rulesstring[]IDs of rules that matched the input. Empty when no rule matched and the default effect applied.
reasonsstring[]Human-readable explanation when a request is denied. Always an empty array when allowed is true.
decision_idstringUnique identifier for this evaluation — use it to look up the full decision record in the audit trail.
If you previously relied on evaluation_ms in the evaluate response for performance monitoring, that field has been removed from the API response. It is still recorded in the decision audit trail — query resource_type=policy_decision in the audit log to retrieve it.
If no rule matches, the response reflects the default_effect:
{
  "allowed": false,
  "matched_rules": [],
  "reasons": ["Default policy effect: DENY"],
  "decision_id": "dec_9c2d4e"
}

Decision audit trail

Every policy evaluation is recorded automatically. Each decision captures the policy version, matched rules, and a SHA-256 hash of the input for tamper-evidence. Use decision records to trace why a specific request was allowed or denied. The evaluate response includes a decision_id you can reference in audit queries:
curl "https://api.truthlocks.com/v1/audit/events?resource_type=policy_decision&resource_id=dec_7f3a1b" \
  -H "X-API-Key: tl_live_..."
The audit record includes fields that are not part of the evaluate API response, such as evaluation_ms and input_hash:
Decision fieldDescription
decision_idUnique identifier for this evaluation
policy_idThe policy that was evaluated
policy_versionVersion of the policy at evaluation time
allowedWhether the request was allowed
matched_rulesRule IDs that matched the input
evaluation_msTime spent evaluating, in milliseconds (audit trail only)
input_hashSHA-256 hash of the evaluation input

Deleting a policy

Remove a policy from the console by clicking the delete icon on the policy card, or via the API:
curl -X DELETE https://api.truthlocks.com/v1/policies/{id} \
  -H "X-API-Key: tl_live_..."
Deleting a policy takes effect immediately. Any pending mint or verify requests that were subject to the policy will no longer be evaluated against it.

Example: multi-rule policy

Combine multiple rules to express complex requirements. Rules are evaluated in order — the first match wins.
{
  "rules": [
    {
      "id": "block_individual",
      "description": "Block individual-tier issuers",
      "conditions": [
        { "field": "trust_tier", "op": "eq", "value": "individual" }
      ],
      "effect": "DENY"
    },
    {
      "id": "allow_us_eu",
      "description": "Allow US or EU jurisdictions",
      "conditions": [
        { "field": "jurisdiction", "op": "in", "value": ["US", "EU"] }
      ],
      "effect": "ALLOW"
    }
  ],
  "default_effect": "DENY"
}
This policy denies individual-tier issuers regardless of jurisdiction, allows US and EU issuers at any other trust tier, and denies everyone else.

Policy engine SDK

Evaluate policies programmatically using the SDK for custom integration scenarios.

Policy API reference

Create, list, evaluate, and delete policies via the REST API.

Compliance exports

Export audit data in SOC 2, GDPR, and HIPAA formats for compliance reporting.

Governance

Use governance workflows for issuer approval and status changes.