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

# Applying Query Policies

> Attach a query policy to an access rule so partners can join on, count, or hash a column without returning its raw values

An access rule decides which of your columns a partner can reach. A [query policy](/concepts/primitives/access-rules#query-policies) decides what they can do with each one: return it, group by it, join on it, or use it only inside functions you list. This guide shows how to write a policy, attach it to an access rule, check it, and remove it.

<Info>
  Query policies are managed through the API. You need an API key for the company that owns the access rule.
</Info>

## Prerequisites

* An access rule your company owns, and its ID
* An API key for your company
* Familiarity with [access rules](/concepts/primitives/access-rules)

## Select the columns in the access rule

A policy can only name columns the access rule's NQL selects. A Rosetta Stone attribute counts only when the NQL selects it from `_rosetta_stone`. Selecting a dataset column that is mapped to the attribute is not enough.

The examples in this guide use an access rule named `customer_segments_ar`, shared by a company with the slug `acme`:

```sql theme={null}
SELECT
  company_data.customer_segments._rosetta_stone.email AS email,
  company_data.customer_segments.segment_id
FROM company_data.customer_segments
```

<Note>
  Once an access rule has a policy, partners can read a Rosetta Stone attribute through it only if the NQL selects that attribute from `_rosetta_stone`. Select every attribute partners need this way before you attach the policy.
</Note>

## Write the policy

Start from what partners should be able to do, and open only that. Any usage the policy doesn't set for a column falls back to `default`, and any usage `default` doesn't set is denied.

Pick the pattern closest to your use case:

<Tabs>
  <Tab title="Join only">
    Partners can join on email addresses to enrich their own records, but never return one. The join-required rule means the access rule is usable only when joined to the partner's own data on email.

    ```json theme={null}
    {
      "default": {
        "projection": { "mode": "allow" },
        "group_by": { "mode": "allow" },
        "join": { "mode": "deny" }
      },
      "usage_rules": [
        {
          "attribute": "email",
          "projection": { "mode": "deny" },
          "group_by": { "mode": "deny" },
          "join": { "mode": "allow" }
        }
      ],
      "join_required_rule": {
        "match": [
          { "keys": [ { "attribute": "email", "form": "raw" } ] }
        ]
      }
    }
    ```

    A partner query that satisfies this policy:

    ```sql theme={null}
    SELECT
      c.customer_id,
      ar.segment_id
    FROM acme."customer_segments_ar" ar
    INNER JOIN company_data.crm c
      ON ar.email = c.email_address
    ```
  </Tab>

  <Tab title="Hashed values only">
    Partners can return email addresses only as SHA-256 hashes, and join only on the hash. Other columns are unrestricted.

    ```json theme={null}
    {
      "default": {
        "projection": { "mode": "allow" },
        "group_by": { "mode": "allow" },
        "join": { "mode": "allow" }
      },
      "usage_rules": [
        {
          "attribute": "email",
          "projection": { "mode": "restrict", "scalar_operators": ["SHA2"] },
          "group_by": { "mode": "deny" },
          "join": { "mode": "restrict", "scalar_operators": ["SHA2"] }
        }
      ]
    }
    ```

    A partner query that satisfies this policy:

    ```sql theme={null}
    SELECT
      SHA2(ar.email, 256) AS email_sha256,
      ar.segment_id
    FROM acme."customer_segments_ar" ar
    ```
  </Tab>

  <Tab title="Counts only">
    Partners can see each segment and count the email addresses in it, but can't return an address or join on one.

    ```json theme={null}
    {
      "default": {
        "projection": { "mode": "deny" }
      },
      "usage_rules": [
        {
          "attribute": "email",
          "projection": { "mode": "restrict", "aggregate_operators": ["COUNT", "COUNT_DISTINCT"] }
        },
        {
          "field": "segment_id",
          "projection": { "mode": "allow" },
          "group_by": { "mode": "allow" }
        }
      ]
    }
    ```

    A partner query that satisfies this policy:

    ```sql theme={null}
    SELECT
      ar.segment_id,
      COUNT(DISTINCT ar.email) AS emails
    FROM acme."customer_segments_ar" ar
    GROUP BY ar.segment_id
    ```

    `COUNT_DISTINCT` is listed separately because `COUNT` alone does not permit `COUNT(DISTINCT …)`. A policy sets no minimum group size, so a count can be as small as one.
  </Tab>
</Tabs>

Grouping by a column and returning it are separate usages. To return each segment next to its count, as the counts example does, `segment_id` needs `projection` set to `allow` as well as `group_by`.

<Warning>
  A policy doesn't restrict `WHERE` filters. A partner who can't return `email` can still run `WHERE ar.email = 'person@example.com'` to check whether an address is present. If partners must not filter on a column, leave it out of the access rule's NQL.
</Warning>

For every option, including nested columns and external functions such as `AI_COMPLETE`, see the [Query Policies reference](/reference/access-rules/query-policies).

## Attach the policy

Send the policy as the body of `PUT /v2/access-rules/{access_rule_id}/query-policy`. This replaces any policy the access rule already has.

```bash theme={null}
curl -X PUT "https://api.narrative.io/v2/access-rules/1234/query-policy" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "default": {
      "projection": { "mode": "deny" }
    },
    "usage_rules": [
      {
        "attribute": "email",
        "projection": { "mode": "restrict", "aggregate_operators": ["COUNT", "COUNT_DISTINCT"] }
      },
      {
        "field": "segment_id",
        "projection": { "mode": "allow" },
        "group_by": { "mode": "allow" }
      }
    ]
  }'
```

A `200` response returns the stored policy. The policy applies to every query compiled after that.

To create an access rule with a policy already attached, add the same object as `query_policy` in the body of [`POST /v2/access-rules`](/api-reference/access-rules/create-a-new-access-rule).

## Fix validation errors

Narrative checks the policy against the access rule's NQL before storing it. An invalid policy returns `400`, leaves the current policy in place, and lists every problem in `error_description`:

```json theme={null}
{
  "error": "invalid_request",
  "error_description": "invalid query policy: usage_rules[attribute=email]: attribute 'email' is not exposed by this access rule"
}
```

The most common errors:

| Message | Fix |
| - | - |
| `attribute 'email' is not exposed by this access rule` | Select the attribute from `_rosetta_stone` in the access rule's NQL, or remove its rule |
| `attribute 'email' has more than one rule` | Merge the entries. A `field` entry for a column that renames an attribute counts as an entry for that attribute |
| `COUNT is an aggregate or external function, not a scalar one` | Move the function to `aggregate_operators` or `external_operators` |
| `attribute 'email' is a raw key, but its join rule is restrict` | Set the key's `form` to the operator its `join` rule lists, or set `join` to `allow` |

The [Query Policies reference](/reference/access-rules/query-policies#validation) lists every message.

## Check the policy

Read the stored policy back:

```bash theme={null}
curl "https://api.narrative.io/v2/access-rules/1234/query-policy" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

The response is the policy object. A `404` means the access rule has no policy. Partners the access rule is shared with can call the same endpoint, and the policy also appears as `query_policy` in every v2 access rule response.

## Change or remove the policy

To change the policy, send the complete new policy with `PUT`. There is no partial update: rules you leave out of the new policy are gone.

To remove the policy, send `DELETE`:

```bash theme={null}
curl -X DELETE "https://api.narrative.io/v2/access-rules/1234/query-policy" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

A `204` response means the access rule has no policy, whether or not it had one before. Partners can then use every column the access rule selects without restriction.

## When a partner's query returns fewer rows

A query that breaks a policy doesn't fail. Narrative leaves that access rule out of the query, so it contributes no rows, and the query returns rows from any other access rules it reads. A partner who expects rows from your access rule and gets none should compare their query against the policy:

* Does the query return a column's raw value where the policy only allows functions of it?
* Does every function on a restricted column appear in the matching list?
* If the policy has a join-required rule, does the query inner join the access rule directly to one of the partner's own datasets on the required keys?

The [Query Policies reference](/reference/access-rules/query-policies#what-each-usage-covers) describes exactly how each query clause is checked.

## Related content

<CardGroup cols={2}>
  <Card title="Access Rules" icon="key" href="/concepts/primitives/access-rules#query-policies">
    Why query policies exist and how Narrative enforces them
  </Card>

  <Card title="Query Policies Reference" icon="shield-halved" href="/reference/access-rules/query-policies">
    Every field, mode, and validation error
  </Card>

  <Card title="Joining Datasets" icon="link" href="/guides/nql/joining-datasets">
    Write the joins a join-required rule expects
  </Card>

  <Card title="Set a Query Policy API" icon="code" href="/api-reference/access-rules/set-the-query-policy-of-an-access-rule">
    Request and response schemas
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.