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

# Allow & Block Lists

> Reject or whitelist requests by source IP, CIDR block, end-user ID, or country before they hit your policy engine

Access lists are a per-project (or per-user) firewall that runs **before** the policy engine. They're the right tool for "I know I never want to hear from this source again" — block in O(1), no ML inference, no upstream cost.

## Targets

Each rule blocks or allows a single value of one of these target types:

| Target type | Matches against                                             | Example value    |
| ----------- | ----------------------------------------------------------- | ---------------- |
| `ip`        | Exact source IP of the inbound request                      | `203.0.113.10`   |
| `ip_cidr`   | Source IP inside a CIDR block                               | `203.0.113.0/24` |
| `end_user`  | The `X-End-User` header on the inbound request              | `customer-42`    |
| `country`   | ISO-3166 alpha-2 code, looked up via the cached GeoIP table | `RU`             |

Country rules are **cache-only**: PromptGuard does not call out to a GeoIP service on the hot path. If the inbound IP isn't in the cache yet, country rules are skipped for that request and the cache is populated lazily by the dashboard's geo distribution endpoint. You can pre-warm the cache by visiting the Overview page.

## Semantics

The decision logic is intentionally boring and predictable:

1. **Block always wins.** If any active block rule matches the request, reject with `403 access_list_block`. Done.
2. **Non-empty allow list flips to default-deny.** If the project (or user) has any active allow rule, the request must match at least one allow rule to proceed. Otherwise reject.
3. **Otherwise, allow.** No rules and no allow list = no opinion = let the policy engine decide.

## Scope: project vs. user

Each rule lives at one of two scopes:

* **Project-scoped** (`project_id` set) — the tightest scope. Use when one project needs different access rules from the rest of your account.
* **User-scoped** (`project_id` is `NULL`) — applies to **every** project you own. Convenient default for "block this abusive user across all my apps".

`check_access` evaluates both scopes in the same query, so a user-wide block plus a project-scoped allow behave exactly the way you'd expect: the block fires.

## Recipes

<AccordionGroup>
  <Accordion title="Block a specific abusive end-user across all projects">
    ```bash theme={"system"}
    curl -X POST https://api.promptguard.co/dashboard/access-lists \
      -H "Authorization: Bearer $TOKEN" -H "X-CSRF-Token: $CSRF" \
      -H "Content-Type: application/json" \
      -d '{
        "list_type": "block",
        "target_type": "end_user",
        "value": "customer-42",
        "reason": "Repeatedly attempted PII extraction"
      }'
    ```

    No `project_id` → user-wide. The next call from this user, on any project, returns `403`.
  </Accordion>

  <Accordion title="Lock one project to a single allowed CIDR">
    ```bash theme={"system"}
    curl -X POST https://api.promptguard.co/dashboard/access-lists \
      -H "Authorization: Bearer $TOKEN" -H "X-CSRF-Token: $CSRF" \
      -H "Content-Type: application/json" \
      -d '{
        "project_id": "...",
        "list_type": "allow",
        "target_type": "ip_cidr",
        "value": "10.0.0.0/8"
      }'
    ```

    The first allow rule on the project flips it to default-deny. Anything outside `10.0.0.0/8` is rejected.
  </Accordion>

  <Accordion title="Country-block with an expiry">
    ```bash theme={"system"}
    curl -X POST https://api.promptguard.co/dashboard/access-lists \
      -H "Authorization: Bearer $TOKEN" -H "X-CSRF-Token: $CSRF" \
      -H "Content-Type: application/json" \
      -d '{
        "list_type": "block",
        "target_type": "country",
        "value": "KP",
        "expires_at": "2026-12-31T00:00:00Z",
        "reason": "Sanctions"
      }'
    ```

    `expires_at` lets the rule auto-deactivate without a follow-up DELETE.
  </Accordion>
</AccordionGroup>

## What gets denied

A blocked request returns `403` with this body:

```json theme={"system"}
{
  "error": {
    "message": "Repeatedly attempted PII extraction",
    "type": "access_denied",
    "code": "access_list_block",
    "rule_id": "dd04502f-...",
    "target_type": "end_user"
  }
}
```

The `rule_id` is the access-list rule that fired, so your client (or your support team) can immediately point at the row in the dashboard.
