# Exceptions

An exception exempts one part of a request from one [Web Application Firewall](/en/documentation/platform/firewall/#waf) (WAF) internal rule, or from all of them, so a pattern that rule would score stops being scored there. The rest of the rule keeps working: everywhere the exception does not match, the rule fires as before. Azion Console calls the object an allowed rule and lists it on the **Allowed Rules** tab of a rule set; the Azion API and the Azion CLI call the same object an exception.

An exception belongs to one rule set and runs where that rule set runs, which is a [Firewall](/en/documentation/platform/firewall/) with the WAF module active. It can be written from a request WAF already marked as a threat, or created ahead of time for a test.

This page lists the fields of an exception, the fifteen match zones its conditions take, the Tuning screen that produces exceptions in bulk, and the API and CLI surfaces that manage them.

---

## Fields

An exception carries six fields a request can set. Three more are set by the platform and are returned in a response, never sent.

| Field           | Type                                   | Required  | Default    | Description                                                                                                         |
| --------------- | -------------------------------------- | --------- | ---------- | ------------------------------------------------------------------------------------------------------------------- |
| `id`            | integer                                | Read-only | —          | The identifier every later call uses                                                                                |
| `rule_id`       | integer                                | No        | `0`        | The internal rule the exception applies to. `0` means all rules. The Console renders it as the **Rule ID** dropdown |
| `name`          | string, 1 to 255 characters            | Yes       | —          | Free text saying what the exception is for. The Console renders it as the **Description** field                     |
| `path`          | string, up to 255 characters, nullable | No        | —          | Restricts the exception to one request path. An exception with no `path` applies wherever its conditions match      |
| `conditions`    | array of objects, at least 1 entry     | Yes       | —          | The parts of the request the exception covers. The Console renders one entry as the **Condition** field             |
| `operator`      | string                                 | No        | `contains` | How the strings in the exception are compared: `contains` or `regex`                                                |
| `active`        | boolean                                | No        | `true`     | Whether the exception is in force. An inactive exception stays on the rule set and stops exempting anything         |
| `last_editor`   | string                                 | Read-only | —          | The email of the account that last changed the exception                                                            |
| `last_modified` | date-time                              | Read-only | —          | When the exception last changed                                                                                     |

The smallest exception the API accepts carries a `name` and one condition. It then applies to all rules, on every path, with `contains`, and is active.

`rule_id` takes one identifier from the internal rule set, and `0` stands for every rule in it. For the list of identifiers and what each rule detects, refer to [WAF Rule Sets](/en/documentation/platform/firewall/waf/rules-set/#internal-rules). The same identifiers name the rows on the Tuning screen, and the Console shows each rule's own description beside its identifier.

`last_editor` and `last_modified` are returned by the API and the CLI. The Console form does not show them.

### Conditions

A condition names one match zone, and where the zone is specific, the string that zone applies to. The shape of a condition is fixed by its `match` value, and there are three.

| Shape               | Keys                | Match zones that take it                                                                      |
| ------------------- | ------------------- | --------------------------------------------------------------------------------------------- |
| Generic             | `match`             | The nine zones whose name carries no `specific`                                               |
| Specific on a name  | `match` and `name`  | `specific_body_form_field_name`, `specific_http_header_name`, `specific_query_string_name`    |
| Specific on a value | `match` and `value` | `specific_body_form_field_value`, `specific_http_header_value`, `specific_query_string_value` |

A condition's `name` or `value` holds between 1 and 255 characters. A `specific_*` condition sent with neither returns `400` with `10059 Required Field`, and one sent with an empty string returns `400` with `10018 Blank Field`.

```json
{ "match": "any_query_string_value" }
```

```json
{ "match": "specific_query_string_name", "name": "q" }
```

```json
{ "match": "specific_query_string_value", "value": "o-neill" }
```

One exception carries more than one condition, and every entry is kept.

### Operator

`operator` decides how the strings in the exception are compared, and it governs two places at once: the exception's `path` and the condition's `name` or `value`.

Under the default `contains`, both are substrings and neither is read as a pattern.

Under `regex`, both are regular expressions, and each is validated separately. A malformed `path` returns `400` with `26008 Invalid Regex Value` and a `source` pointer of `/data/path`; a malformed condition `name` returns the same code with a pointer of `/data/conditions/0/name`. **There is no way to make one a regular expression and the other a literal** — `regex` widens both together, so an exception that needs a pattern in its condition also needs one in its `path`.

The Console renders the choice as the **Operator** dropdown, with the placeholder `Select an operator`. There is no separate regex switch anywhere in the interface.

---

## Match zones

A match zone is the part of a request a condition compares. The API takes it as `conditions[].match`. The Azion Console labels the field **Condition**, not match zone, and seeds it with *Any HTTP Header Value*.

There are fifteen, listed below in the order the Console offers them. An option beginning `Specific` reveals an extra **Name** or **Value** field, and which of the two appears is fixed by the option.

| Console option                 | API value                        | Condition shape     | What it compares                                                                                   |
| ------------------------------ | -------------------------------- | ------------------- | -------------------------------------------------------------------------------------------------- |
| Any HTTP Header Value          | `any_http_header_value`          | Generic             | The value of every request header, such as `Mozilla/5.0` or `application/json`                     |
| Any HTTP Header Name           | `any_http_header_name`           | Generic             | The name of every request header, such as `User-Agent` or `Authorization`                          |
| Specific HTTP Header Value     | `specific_http_header_value`     | Specific on a value | The `value` in the condition, against the value of every request header. It carries no header name |
| Specific HTTP Header Name      | `specific_http_header_name`      | Specific on a name  | The `name` in the condition, such as `cookie`, against the name of a request header                |
| Any Query String Value         | `any_query_string_value`         | Generic             | The value of every query string parameter. In `?id=123&user=admin`, `123` and `admin`              |
| Any Query String Name          | `any_query_string_name`          | Generic             | The name of every query string parameter. In `?id=123&user=admin`, `id` and `user`                 |
| Specific Query String Value    | `specific_query_string_value`    | Specific on a value | The `value` in the condition, against the value of every query string parameter                    |
| Specific Query String Name     | `specific_query_string_name`     | Specific on a name  | The `name` in the condition, such as `token`, against the name of a query string parameter         |
| Body Form Field Value          | `body_form_field_value`          | Generic             | The value of every form field in the request body                                                  |
| Body Form Field Name           | `body_form_field_name`           | Generic             | The name of every form field in the request body                                                   |
| Specific Body Form Field Value | `specific_body_form_field_value` | Specific on a value | The `value` in the condition, against the value of every form field in the body                    |
| Specific Body Form Field Name  | `specific_body_form_field_name`  | Specific on a name  | The `name` in the condition, such as `api_key`, against the name of a form field in the body       |
| Any URL                        | `any_url`                        | Generic             | The request URL                                                                                    |
| Raw Body                       | `raw_body`                       | Generic             | The uninterpreted request body, such as a JSON or an XML payload                                   |
| File Extension                 | `file_extension`                 | Generic             | The file extension in the request, such as `.php`, `.exe` or `.sh`                                 |

A value outside this set returns `400` with `10039 Invalid Choice` and a `source` pointer naming the condition, such as `/data/conditions/0/match`.

**`specific_http_header_value` does not name a header.** Its two keys are `match` and `value`, so the string it carries is compared against header **values**, not against a header name. A condition of `{"match": "specific_http_header_value", "value": "Cookie"}` therefore matches any header whose value contains `Cookie`, which is not the same thing as an exception for the `Cookie` header.

**`specific_http_header_name` with a `name` is the only shape that scopes an exception to one named header.** The API stores the string exactly as it is sent, so `cookie`, `Cookie`, and `HTTP_COOKIE` are all accepted and all read back unchanged.

> **Caution**
>
> A key the condition shape does not carry is dropped, not rejected. A condition sent as `{"match": "any_http_header_value", "name": "cookie"}` returns `202` and reads back as `{"match": "any_http_header_value"}`, with the `name` gone. The exception then covers every request header instead of one, and nothing in the response, the read-back, or the Console says so. Use a `specific_*` match zone whenever the exception is meant to name one header, one query string parameter, or one form field.

---

## Tuning

**Tuning** is a tab on a rule set. It lists the requests each internal rule matched over a chosen window, grouped by rule ID, and turns selected records into exceptions in bulk. It is where a false positive is found before it is allowed.

A query needs a domain. The other filters narrow it.

| Filter        | What it narrows                                                                                                                                                                                                   |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Domain        | The workload or domain whose requests are read. Required                                                                                                                                                          |
| Time Range    | The window the requests are read over. The options are `Last 1 hour`, `Last 3 hours`, `Last 6 hours`, `Last 12 hours`, `Last day`, `Last 2 days` and `Last 3 days`. `Last 3 days` is the longest window available |
| Network Lists | The requests whose source address a [Network Lists](/en/documentation/platform/firewall/network-shield/network-lists/) entry holds                                                                                |
| IP Address    | The requests from one or more source addresses                                                                                                                                                                    |
| Country       | The country the request came from                                                                                                                                                                                 |

Azion Console refuses two combinations: **IP Address** with an *IP/CIDR* network list filter, and **Country** with a *Countries* network list filter.

The result is one row per internal rule that matched, carrying the columns **Rule ID**, **Hits**, **Paths**, **IPs**, **Countries**, **Top 10 IP Addresses** and **Top 10 Countries**, above a count of the records found.

Selecting a row opens **More Details**, which lists the occurrences behind that rule ID and narrows them further by network list, country, IP address, and **Path**.

Selecting rows and choosing **Allow Rules** writes them to the rule set's allowed rules. The confirmation asks for the reason these rules are being allowed, and states that a separate rule is created for each possible attack on each URI, so one selection produces several exceptions. The **Allowed Rules** tab carries a **Create from Tuning** button back into this screen.

For the procedure, refer to [Tune a WAF rule set](/en/documentation/guides/application-security/firewall-and-waf/tune-waf/). For why tuning is repeated rather than done once, refer to [Scoring and modes](/en/documentation/platform/firewall/waf/scoring-and-modes/).

---

## API

Every operation is authenticated and sits under `https://api.azion.com/v4/workspace/wafs/{waf_id}/exceptions`. A request carries a token from [Personal Tokens](/en/documentation/fundamentals/personal-tokens/) in the `Authorization` header under the `Token` scheme, and a request with a body also carries `Content-Type: application/json`. The API manages exceptions independently of the Console, so one can be created, changed, or removed from a script.

| Operation                   | Method and path                                   |
| --------------------------- | ------------------------------------------------- |
| Create an exception         | `POST /wafs/{waf_id}/exceptions`                  |
| List exceptions             | `GET /wafs/{waf_id}/exceptions`                   |
| Retrieve an exception       | `GET /wafs/{waf_id}/exceptions/{exception_id}`    |
| Replace an exception        | `PUT /wafs/{waf_id}/exceptions/{exception_id}`    |
| Update part of an exception | `PATCH /wafs/{waf_id}/exceptions/{exception_id}`  |
| Delete an exception         | `DELETE /wafs/{waf_id}/exceptions/{exception_id}` |

A create, a replace, a partial update, and a delete answer `202`. A read answers `200`.

The operations above are ordinary API calls, so exception management belongs in a delivery pipeline like any other configuration change. The evidence an exception is written from comes from a different surface: Tuning is an Azion Console screen, and the API does not expose it. A script reads that evidence from [Real-Time Events](/en/documentation/platform/real-time-events/), where `wafMatch` names the internal rules a request matched and `wafScore` reports the score each threat family reached. It then creates an exception for each false positive those records confirm. The Azion CLI is the other surface, and its `--conditions` flag does not work on 4.23.0, so a script uses `--file` or the API directly.

### Create an exception

```bash
curl --request POST \
  --url https://api.azion.com/v4/workspace/wafs/12345/exceptions \
  --header 'Accept: application/json' \
  --header 'Authorization: Token [TOKEN VALUE]' \
  --header 'Content-Type: application/json' \
  --data '{
  "rule_id": 1013,
  "name": "docs-rework-waf-exc-generic",
  "path": "/search",
  "operator": "contains",
  "active": true,
  "conditions": [
    { "match": "any_query_string_value" }
  ]
}'
```

The response carries `202`, and not `201`:

```json
{
  "state": "pending",
  "data": {
    "id": 123456,
    "rule_id": 1013,
    "name": "docs-rework-waf-exc-generic",
    "path": "/search",
    "conditions": [
      { "match": "any_query_string_value" }
    ],
    "operator": "contains",
    "active": true,
    "last_editor": "[ACCOUNT EMAIL]",
    "last_modified": "2026-01-01T12:00:00.000000Z"
  }
}
```

The `state` of `pending` says the exception was accepted, and the `id` in `data` is the handle every later call uses. `last_editor` carries the email of the account that last changed the exception, and the value above is a placeholder.

The same call with the body below creates an exception scoped to the query string parameter named `q`. The response carries `202`, a new `id` of `123457`, and the condition echoed unchanged.

```json
{
  "rule_id": 1013,
  "name": "docs-rework-waf-exc-specific",
  "path": "/search",
  "operator": "contains",
  "active": true,
  "conditions": [
    { "match": "specific_query_string_name", "name": "q" }
  ]
}
```

The body below scopes the same exception to a query string value instead. It answers `202` with an `id` of `123458`.

```json
{
  "rule_id": 1013,
  "name": "docs-rework-waf-exc-value",
  "path": "/search",
  "operator": "contains",
  "active": true,
  "conditions": [
    { "match": "specific_query_string_value", "value": "o-neill" }
  ]
}
```

### List exceptions

```bash
curl --request GET \
  --url https://api.azion.com/v4/workspace/wafs/12345/exceptions \
  --header 'Accept: application/json' \
  --header 'Authorization: Token [TOKEN VALUE]'
```

The response carries `200` and the collection envelope below, with one exception per entry under `results`, each in the shape the create response carries.

| Field         | What it carries                                               |
| ------------- | ------------------------------------------------------------- |
| `count`       | Exceptions the rule set holds                                 |
| `total_pages` | Pages the result divides into, at the current `page_size`     |
| `page`        | The page this response carries                                |
| `page_size`   | Exceptions per page. Defaults to 10                           |
| `next`        | The URL of the following page, or `null`                      |
| `previous`    | The URL of the preceding page, or `null`                      |
| `results`     | One exception per entry, carrying the fields listed in Fields |

The endpoint accepts `created_at__gte`, `created_at__lte`, `description`, `fields`, `id`, `last_editor`, `last_modified__gte`, `last_modified__lte`, `ordering`, `page`, `page_size`, `path`, and `search` as query parameters. `page_size` runs from 1 to 100 and defaults to 10. A value above 100 returns `400` with `10097 Invalid Page Size`.

### Retrieve, update, and delete an exception

`GET /wafs/{waf_id}/exceptions/{exception_id}` answers `200` and returns the exception under `data`, with no `state` key.

`PUT /wafs/{waf_id}/exceptions/{exception_id}` replaces an exception and takes the same body as a create. It answers `202` with a `state` of `pending`. The `conditions` array is replaced rather than merged, so a replace sends every condition the exception is to keep.

`PATCH /wafs/{waf_id}/exceptions/{exception_id}` takes only the fields sent and answers `202`. A body of `{"active": false}` deactivates an exception and leaves the rest of it as it was.

`DELETE /wafs/{waf_id}/exceptions/{exception_id}` answers `202` with an empty body.

---

## CLI

The [Azion CLI](/en/documentation/devtools/cli/) manages exceptions under the `waf-exceptions` noun. The flags below are the ones Azion CLI 4.23.0 carries, without the global flags every command takes.

| Command                         | What it does                          |
| ------------------------------- | ------------------------------------- |
| `azion create waf-exceptions`   | Creates an exception on a rule set    |
| `azion list waf-exceptions`     | Lists the exceptions a rule set holds |
| `azion describe waf-exceptions` | Returns one exception                 |
| `azion update waf-exceptions`   | Changes an exception                  |
| `azion delete waf-exceptions`   | Removes an exception                  |

`azion create waf-exceptions` and `azion update waf-exceptions` share their flags, except that `--exception-id` belongs to the update.

| Flag             | What it sets                                                               |
| ---------------- | -------------------------------------------------------------------------- |
| `--waf-id`       | The rule set the exception belongs to                                      |
| `--name`         | The exception name                                                         |
| `--rule-id`      | The internal rule the exception applies to                                 |
| `--path`         | The path the exception is restricted to                                    |
| `--operator`     | `regex` or `contains`                                                      |
| `--active`       | `true` or `false`. Defaults to `true`                                      |
| `--conditions`   | The conditions, in JSON. Refer to the caution below before using it        |
| `--file`         | A JSON file carrying the body, or `-` to read the body from standard input |
| `--exception-id` | The exception to change. `azion update waf-exceptions` only                |

`azion list waf-exceptions` takes `--waf-id`, `--details`, `--filter` to filter by name, `--order-by`, `--page` with a default of `1`, and `--page-size` with a default of `50`. `azion describe waf-exceptions` and `azion delete waf-exceptions` each take `--waf-id` and `--exception-id`.

> **Caution**
>
> On 4.23.0, `azion create waf-exceptions --conditions` sends an empty `conditions` array whatever value it is given, and the API rejects the request with `400` and `10049 Min Length List Field` on `/data/conditions`. Create the exception from a file with `--file` instead. In the same version, the `RULE ID` column of `azion list waf-exceptions` prints a value that is not the rule ID; read the rule ID with `azion describe waf-exceptions` or through the API.

Write the body to a file:

```json
{
  "rule_id": 1013,
  "name": "docs-rework-waf-exc-cli-file",
  "path": "/",
  "operator": "contains",
  "active": true,
  "conditions": [
    { "match": "any_query_string_value" }
  ]
}
```

Then create the exception from it:

```bash
azion create waf-exceptions --waf-id 12345 --file exc.json
```

```text
Created WAF Exception with ID 123459
```

`azion describe waf-exceptions` returns one exception, with the keys sorted by the CLI:

```bash
azion describe waf-exceptions --waf-id 12345 --exception-id 123457 --format json
```

```json
{"active": true, "conditions": [{"match": "specific_query_string_name", "name": "q"}],
 "id": 123457, "last_editor": "[ACCOUNT EMAIL]",
 "last_modified": "2026-01-01T12:00:02.000000Z", "name": "docs-rework-waf-exc-specific",
 "operator": "contains", "path": "/search", "rule_id": 1013}
```

---

## Errors

A rejected request returns an `errors` array. Each entry carries a `code`, a `title`, a `detail`, the `status`, and a `source` pointer naming the field the rejection is about. For a condition the pointer indexes into the array, so `/data/conditions/0/name` names the first condition:

```json
{
  "errors": [
    {
      "code": "10059",
      "title": "Required Field",
      "detail": "This field is required.",
      "status": "400",
      "source": {
        "pointer": "/data/conditions/0/name"
      }
    }
  ]
}
```

| Code    | Title                  | Status | What causes it                                                                                               | What to do                                                                                               |
| ------- | ---------------------- | ------ | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| `10004` | Not Found              | 404    | A `waf_id` or an `exception_id` that does not exist                                                          | Check every segment of the path                                                                          |
| `10009` | Unsupported Media Type | 415    | A write sent with a `Content-Type` other than `application/json`                                             | Send `Content-Type: application/json`                                                                    |
| `10018` | Blank Field            | 400    | A condition `name` or `value` that is an empty string                                                        | Send 1 to 255 characters                                                                                 |
| `10039` | Invalid Choice         | 400    | A value outside an enum: `rule_id`, `operator`, or a condition's `match`                                     | Send one of the values listed in Fields and Match zones. The `source` pointer names the field            |
| `10046` | Max Length             | 400    | A `name` longer than 255 characters                                                                          | Shorten the name                                                                                         |
| `10049` | Min Length List Field  | 400    | An empty `conditions` array, which is what `azion create waf-exceptions --conditions` always sends           | Send at least one condition, through the API or `azion create waf-exceptions --file`                     |
| `10059` | Required Field         | 400    | A required field is absent, such as a `specific_*` condition with no `name`                                  | Add the field the `source` pointer names                                                                 |
| `10097` | Invalid Page Size      | 400    | A `page_size` above 100 on a list request                                                                    | Ask for 100 or fewer                                                                                     |
| `26008` | Invalid Regex Value    | 400    | `operator` is `regex` and the exception's `path`, or a condition's `name` or `value`, is not a valid pattern | Fix the pattern the `source` pointer names. The entry's `meta.regex_value` repeats the value that failed |

A `rule_id` of `0` is not an error. It is the legal value meaning all rules, and it answers `202` like any other.

---

## Related resources

- [Scoring and modes](/en/documentation/platform/firewall/waf/scoring-and-modes.md): The scoring model an exception exempts a request from, and the tuning loop around it.
- [Firewall best practices](/en/documentation/platform/firewall/best-practices.md#waf): When an exception is the right answer, the risk each one carries, and how to keep the set small.
- [Create a WAF exception](/en/documentation/guides/application-security/firewall-and-waf/configure-waf-allowed-rules.md): The procedure that creates an exception in Azion Console, with a worked false positive.
- [Tune a WAF rule set](/en/documentation/guides/application-security/firewall-and-waf/tune-waf.md): The steps that run the Tuning screen and turn its rows into exceptions.
