---
sidebar_label: Policies
toc_max_heading_level: 3
doc_id: a4e7868e-5dbb-4ebf-820d-f4794b2f9dac
description: >-
  Policies define approval conditions using metadata to ensure business rules,
  security, and compliance alignment.
keywords:
  - policies
  - approval automation
  - metadata
  - compliance
  - access control
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';


# Policies

## What is a policy?

**Policies** define the conditions under which approval actions are granted, denied, or flagged for manual review.

- They help ensure that approvals align with business rules, security requirements, and compliance policies.
- By leveraging metadata, policies can evaluate real-time information to make approval decisions.

## How policies are evaluated

When an action is triggered, nullplatform builds a **context object** containing the relevant metadata for that action — the user, scope, build, application, and so on. The policy's conditions are then evaluated against that context.

The evaluation returns one of three outcomes:

| Outcome | Meaning |
|---|---|
| `approve` | All conditions passed. The action is auto-approved. |
| `deny` | One or more conditions failed. The action is auto-denied. |
| `manual` | The result is inconclusive and requires a human reviewer. |

Which outcome is ultimately applied depends on the `on_policy_success` and `on_policy_fail` settings on the **approval action** linked to the policy. For example, an action with `"on_policy_success": "approve"` and `"on_policy_fail": "manual"` will auto-approve passing requests and send failing ones to a reviewer.

```mermaid
flowchart LR
  A[Action triggered] --> B[Context is built]
  B --> C{Conditions evaluated}
  C -->|All pass| D[on_policy_success]
  C -->|Any fail| E[on_policy_fail]
  D --> F["approve or manual"]
  E --> G["deny or manual"]
```

## Using metadata in policies

Policies can leverage metadata from different entities in the catalog based on the action being evaluated. This allows for precise, context-aware approval decisions. For example, you can enforce approvals based on:

- Code coverage in a build process.
- The security status of a deployment.
- Whether a resource supports auto-scaling.
- And more.

By using metadata, policies dynamically adapt to specific requirements.

For more details, see the [Catalog entity](/docs/catalog/getting-started) docs.

#### Available entities for policy configuration

The following table shows which entities provide metadata for different approval actions:

| Approval actions           | Entities whose metadata is available for policy configuration                          |
| -------------------------- | -------------------------------------------------------------------------------------- |
| **deployment:create**      | deployment, user, scope, release, build, application, namespace, account, organization |
| **scope:create**           | scope, user, application, namespace, account, organization                             |
| **scope:recreate**         | scope, user, application, namespace, account, organization                             |
| **scope:write**            | scope, user, application, namespace, account, organization                             |
| **scope:delete**           | scope, user, application, namespace, account, organization                             |
| **scope:stop**             | scope, user, application, namespace, account, organization                             |
| **parameter:read-secrets** | parameter, user, application, namespace, account, organization                         |
| **service:action**         | user, application, namespace, account                                                  |

This means that once you've defined a policy using metadata, you can associate it with an approval action based on the entities it references.

For example, if your policy relies on **scope metadata**, you can link it to any approval action listed above, as scope metadata is broadly available. However, if your policy references **build metadata** — like in the example below — it can only be applied to a **deployment:create** action, since build metadata is exclusive to deployments.

> **Note:** For `parameter:read-secrets`, the context also includes the `dimensions` of the access request (the dimensions of the values being revealed), so conditions can reference them directly — for example, `"dimensions.environment": "production"`. See [Accessing secret values](/docs/parameters/secret-visibility#set-a-policy) for a full example.

## Adding metadata to policies

Before using metadata in policies, you need to define a **catalog specification**. This outlines the structure and rules for the data you want to enforce.

For example, the following catalog specification tracks **code coverage** in a **build** entity:

```json
{
  "name": "Coverage",
  "description": "Build coverage report",
  "nrn": "organization=1",
  "entity": "build",
  "metadata": "coverage",
  "schema": {
    "type": "object",
    "properties": {
      "percent": {
        "type": "number",
        "default": 0,
        "minimum": 80,
        "maximum": 100
      },
      "report": {
        "type": "string",
        "format": "uri"
      }
    },
    "required": ["percent", "report"],
    "additionalProperties": false
  }
}
```

See the API reference for more info on how to [create a catalog specification](/docs/api/catalog-specification-create).

Now that the specification is created, policies can reference this metadata to enforce coverage requirements before approving deployments.

## Create a policy

To create a policy, send a [POST request](/docs/api/policy-create) to the approval policy endpoint. The `conditions` object defines the rules that must be met for an approval action to proceed.

:::info Conditions' syntax
Policies use [MongoDB syntax](https://www.mongodb.com/docs/manual/introduction/) for conditions, allowing you to create flexible and complex rules.
:::

### Example: Enforcing approval conditions for deployments

The following policy ensures that deployments are only approved if:

- Code coverage is **at least 80%**.
- Security issues do not exceed **4**.
- The resource supports **auto-scaling**.
- The environment is either **dev** or **qa**.

<Tabs
defaultValue="policy-cli"
values={[
{ label: 'CLI', value: 'policy-cli' },
{ label: 'cURL', value: 'policy-curl' },
]}>

<TabItem value="policy-cli">

```bash
np approval policy create
  --body '{
  "nrn": "organization=1:account=2:namespace=3:application=4",
  "name": "Enforce coverage, security, and scaling for deployments",
  "conditions": {
    "build.metadata.coverage.percent": { "$gte": 80 },
    "build.metadata.security.issues": { "$lte": 4 },
    "scope.capabilities.auto_scaling": true,
    "$or": [
      { "scope.dimensions.environment": "dev" },
      { "scope.dimensions.environment": "qa" }
    ]
  },
  "selector": {}
}'
```
</TabItem>
<TabItem value="policy-curl">

```bash
curl -L -X POST 'https://api.nullplatform.com/approval/policy' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
  "nrn": "organization=1:account=2:namespace=3:application=4",
  "name": "Enforce coverage, security, and scaling for deployments",
  "conditions": {
    "build.metadata.coverage.percent": { "$gte": 80 },
    "build.metadata.security.issues": { "$lte": 4 },
    "scope.capabilities.auto_scaling": true,
    "$or": [
      { "scope.dimensions.environment": "dev" },
      { "scope.dimensions.environment": "qa" }
    ]
  },
  "selector": {}
}'
```

</TabItem>
</Tabs>

### Example: Restricting scope creation by environment

Use this pattern to require manual approval whenever a scope is created directly in production, while allowing development and staging environments to proceed automatically.

```json
{
  "nrn": "organization=1:account=2:namespace=3",
  "name": "Block direct scope creation in production",
  "conditions": {
    "$nor": [
      { "scope.dimensions.environment": "production" }
    ]
  },
  "selector": {}
}
```

When combined with an approval action that has `"on_policy_fail": "manual"`, any scope creation attempt in production is flagged for review instead of going through automatically.

### Example: Restricting access to parameter secrets

This policy narrows who can read secrets by restricting access to a specific application. It's designed for use with the `parameter:read-secrets` action.

```json
{
  "nrn": "organization=1:account=2:namespace=3:application=4",
  "name": "Require approval to read production secrets",
  "conditions": {
    "application.id": { "$eq": 4 }
  },
  "selector": {}
}
```

You can combine this with additional conditions — such as a metadata field on the parameter or application — to enforce more specific access rules.

### Example: Auto-approving secret access by environment

Requests to read secret values carry the dimensions being revealed, so a policy can decide the outcome per environment. This policy passes only for lower environments:

```json
{
  "nrn": "organization=1:account=2:namespace=3:application=4",
  "name": "Auto-approve secret access in lower environments",
  "conditions": {
    "dimensions.environment": { "$in": ["development", "staging"] }
  },
  "selector": {}
}
```

Pair it with a `parameter:read-secrets` approval action that has `"on_policy_success": "approve"` and `"on_policy_fail": "manual"`: requests for `development` or `staging` values are approved automatically, while anything else — including requests for the whole parameter — waits for manual review. Keep the conditions an allowlist (like `$in`) so requests that don't specify dimensions never auto-approve.

For the full setup, see [Accessing secret values](/docs/parameters/secret-visibility#set-a-policy).

#### Example response

If the policy is successfully created, you'll receive a response like this:

```json
{
  "id": 1234,
  "nrn": "organization=1:account=2:namespace=3:application=4",
  "name": "Enforce coverage, security, and scaling for deployments",
  "conditions": {
    "build.metadata.coverage.percent": { "$gte": 80 },
    "build.metadata.security.issues": { "$lte": 4 },
    "scope.capabilities.auto_scaling": true,
    "$or": [
      { "scope.dimensions.environment": "dev" },
      { "scope.dimensions.environment": "qa" }
    ]
  },
  "selector": {}
}
```

:::tip
The `id` field contains the unique policy ID. Be sure to save it, as you'll need it in the next steps.
:::

## Using selectors

A **selector** narrows down which requests a policy applies to, acting as a pre-filter before the conditions are evaluated.

- If the selector **matches** the context, conditions are evaluated normally.
- If the selector **doesn't match**, the policy is skipped entirely for that request.

This is especially useful when multiple policies are attached to the same approval action and you want each one to apply only to a specific subset of requests — for example, targeting a particular service specification.

:::note Using selectors for service actions
We recommend using selectors for services, as they allow you to target specific service specifications. For example:

<details>
  <summary>Service action example</summary>

```json
{
  "nrn": "organization=1:account=2:namespace=3:application=4",
  "name": "This service requires approval",
  "conditions": {
    "$or": [
      {
        "$and": {
          "action.slug": "provision-service",
          "action.parameters.endpoint_type": "private"
        }
      },
      {
        "$nor": [
          { "action.slug": "provision-service" }
        ]
      }
    ]
  },
  "selector": { "service_specification.slug": "my-service" }
}
```
</details>
:::

## Updating a policy

To update a policy's conditions or selector, send a [PATCH request](/docs/api/policy-update) with the fields you want to change:

```bash
curl -L -X PATCH 'https://api.nullplatform.com/approval/policy/1234' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
  "conditions": {
    "build.metadata.coverage.percent": { "$gte": 90 },
    "scope.capabilities.auto_scaling": true
  }
}'
```

You can update `conditions`, `selector`, or both in a single request. Fields not included in the request body remain unchanged. Changes take effect immediately for all new approval requests.

## What's next

Now that you've created a policy, the next step is to link it to an **approval action** so it can be evaluated.

Learn how to [associate a policy with an action.](/docs/approvals/associate-policy-with-action)
