> ## Documentation Index
> Fetch the complete documentation index at: https://docs.truthlocks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Issuance policies

> Define rule-based policies that control who can mint, verify, and export attestations in your tenant.

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`)
* **Effect** — `ALLOW` 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.

```json theme={null}
{
  "rules": [
    {
      "id": "us_only",
      "description": "US jurisdiction required",
      "conditions": [
        { "field": "jurisdiction", "op": "eq", "value": "US" }
      ],
      "effect": "ALLOW"
    }
  ],
  "default_effect": "DENY"
}
```

### Condition operators

| Operator | Description                        | Example                                                                          |
| -------- | ---------------------------------- | -------------------------------------------------------------------------------- |
| `eq`     | Equals                             | `{ "field": "jurisdiction", "op": "eq", "value": "US" }`                         |
| `neq`    | Not equals                         | `{ "field": "trust_tier", "op": "neq", "value": "individual" }`                  |
| `in`     | Matches any value in a list        | `{ "field": "trust_tier", "op": "in", "value": ["verified_org", "enterprise"] }` |
| `nin`    | Does not match any value in a list | `{ "field": "jurisdiction", "op": "nin", "value": ["CN", "RU"] }`                |
| `gt`     | Greater than (numeric)             | `{ "field": "key.age_days", "op": "gt", "value": 90 }`                           |
| `lt`     | Less than (numeric)                | `{ "field": "key.age_days", "op": "lt", "value": 365 }`                          |
| `exists` | Field 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:

| Field             | Description                                                                              |
| ----------------- | ---------------------------------------------------------------------------------------- |
| `jurisdiction`    | The issuer's registered jurisdiction (ISO country code)                                  |
| `trust_tier`      | The issuer's trust tier (`individual`, `verified_org`, `regulated_issuer`, `enterprise`) |
| `status`          | The issuer's current status (`ACTIVE`, `suspended`, `revoked`, `SUNSET`, `pending`)      |
| `risk_rating`     | The issuer's risk rating (`low`, `medium`, `high`, `CRITICAL`)                           |
| `assurance_level` | The issuer's assurance level (`standard`, `high`, `regulated`)                           |
| `key.age_days`    | Age of the signing key in days                                                           |
| `key.status`      | Status of the signing key (`ACTIVE`, `REVOKED`, `EXPIRED`)                               |
| `key.kid`         | Identifier of the signing key                                                            |

## Policy lifecycle

Each policy has a status that controls whether it is enforced:

| Status     | Behavior                                                     |
| ---------- | ------------------------------------------------------------ |
| `DRAFT`    | Saved but not enforced — use this while you iterate on rules |
| `ACTIVE`   | Enforced on every matching request                           |
| `DISABLED` | Temporarily 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](#simulating-a-policy).
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.

<Info>
  Transitioning a policy to `ACTIVE` takes effect immediately. Make sure you have tested the rules with the simulator before activating.
</Info>

## Policy categories

Policies are scoped to a specific action:

| Category        | Applies to                                        |
| --------------- | ------------------------------------------------- |
| `MINT`          | Attestation minting requests                      |
| `VERIFY`        | Verification requests                             |
| `BUNDLE_EXPORT` | Proof-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.

```json theme={null}
{
  "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`:

```bash theme={null}
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:

| Field         | Type    | Description                                                                        |
| ------------- | ------- | ---------------------------------------------------------------------------------- |
| `policy_id`   | string  | The ID of the policy to bind                                                       |
| `target_type` | string  | `ISSUER`, `VERIFICATION_PROFILE`, or `TENANT_DEFAULT`                              |
| `target_id`   | string  | UUID of the target. Omit for `TENANT_DEFAULT` to apply the policy to all entities. |
| `action`      | string  | `MINT`, `VERIFY`, or `BUNDLE_EXPORT`                                               |
| `priority`    | integer | Higher-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

```bash theme={null}
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`:

```bash theme={null}
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
  }'
```

<Tip>
  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.
</Tip>

## 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:

| Template               | Category | Description                                           |
| ---------------------- | -------- | ----------------------------------------------------- |
| Allow US Jurisdiction  | MINT     | Only allow minting from US-based issuers              |
| Verified Org Only      | MINT     | Require `verified_org` trust tier or higher           |
| Allow All (Permissive) | MINT     | Allow all mints — use with caution                    |
| Verify - US & EU Only  | VERIFY   | Only 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.

```bash theme={null}
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"
    }
  }'
```

```json Response theme={null}
{
  "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

```bash theme={null}
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:

```bash theme={null}
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"
    }
  }'
```

```json Response theme={null}
{
  "allowed": true,
  "matched_rules": ["us_only"],
  "reasons": [],
  "decision_id": "dec_7f3a1b"
}
```

The response includes four fields:

| Field           | Type      | Description                                                                                                                                           |
| --------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allowed`       | boolean   | Whether the request passed all active policies                                                                                                        |
| `matched_rules` | string\[] | IDs of rules that matched the input. Empty when no rule matched and the default effect applied.                                                       |
| `reasons`       | string\[] | Human-readable explanation when a request is denied. Always an empty array when `allowed` is `true`.                                                  |
| `decision_id`   | string    | Unique identifier for this evaluation — use it to look up the full decision record in the [audit trail](/security/audit#policy-decision-audit-trail). |

<Note>
  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](#decision-audit-trail) — query `resource_type=policy_decision` in the [audit log](/security/audit#policy-decision-audit-trail) to retrieve it.
</Note>

If no rule matches, the response reflects the `default_effect`:

```json theme={null}
{
  "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:

```bash theme={null}
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 field   | Description                                               |
| ---------------- | --------------------------------------------------------- |
| `decision_id`    | Unique identifier for this evaluation                     |
| `policy_id`      | The policy that was evaluated                             |
| `policy_version` | Version of the policy at evaluation time                  |
| `allowed`        | Whether the request was allowed                           |
| `matched_rules`  | Rule IDs that matched the input                           |
| `evaluation_ms`  | Time spent evaluating, in milliseconds (audit trail only) |
| `input_hash`     | SHA-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:

```bash theme={null}
curl -X DELETE https://api.truthlocks.com/v1/policies/{id} \
  -H "X-API-Key: tl_live_..."
```

<Warning>
  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.
</Warning>

## Example: multi-rule policy

Combine multiple rules to express complex requirements. Rules are evaluated in order — the first match wins.

```json theme={null}
{
  "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.

## Related

<CardGroup cols={2}>
  <Card title="Policy engine SDK" icon="code" href="/policy">
    Evaluate policies programmatically using the SDK for custom integration
    scenarios.
  </Card>

  <Card title="Policy API reference" icon="rectangle-terminal" href="/api-reference/policies/create">
    Create, list, evaluate, and delete policies via the REST API.
  </Card>

  <Card title="Compliance exports" icon="file-export" href="/guides/compliance-exports">
    Export audit data in SOC 2, GDPR, and HIPAA formats for compliance
    reporting.
  </Card>

  <Card title="Governance" icon="scale-balanced" href="/api-reference/governance/list-requests">
    Use governance workflows for issuer approval and status changes.
  </Card>
</CardGroup>
