# Bot Manager quickstart

This guide instructs you through scoring your first request with [Bot Manager Lite](/en/documentation/platform/firewall/bot-manager/bot-manager-lite/), the edition you install for yourself from Marketplace.

- Create a function instance on your firewall, carrying the arguments the function runs on.
- Run that instance from a Rules Engine rule, on every request the firewall receives.
- Send a bot-shaped request and read the score the function gave it.

Bot Manager Lite scores each request against a published set of static rules, which carry signatures for credential stuffing, vulnerability scanning, and site scraping. [Bot Manager](/en/documentation/platform/firewall/#bot-manager), the full edition, is enabled on request through the Azion Service Delivery team, and it adds a dynamic score and Reputation Intelligence on top of those rules. Both editions are configured the same way, so the stages below do not change with the edition.

Five objects put a request under inspection, and each one links to the next:

1. The installed **function** is the Bot Manager Lite code, installed once from Marketplace into the account.
2. The **firewall** carries the **Functions** module, which is what runs an installed function.
3. The **function instance** on that firewall holds the arguments the function runs on.
4. The **Rules Engine rule** on the same firewall carries a `run_function` behavior, which names that instance.
5. The **workload** serving your application is bound to that firewall through its deployment.

An installed function scores nothing on its own. All five objects have to exist.

---

Select the interface you will use. The prerequisites and every stage below follow that choice.

## Prerequisites

- An Azion account.
- Bot Manager Lite installed from Marketplace. The install runs in Azion Console, whatever interface the stages below use: access [Azion Console](https://console.azion.com/) > **Marketplace**, select the Bot Manager Lite integration from the search field, and select **Install**. The function then appears in **Functions**, under **Edge Libraries**, where a **Vendor** column marks it as a Marketplace install. For more information, refer to [Install Bot Manager Lite](/en/documentation/guides/application-development/integrations/bot-manager-lite/).
- A [firewall](/en/documentation/platform/firewall/) with the **Functions** module turned on. The module is in the firewall's **Main Settings**, in the **Modules** section, and a firewall created with the [Azion CLI](/en/documentation/devtools/cli/) has it turned on already. For more information, refer to [Set a firewall's main settings](/en/documentation/guides/application-security/firewall-and-waf/firewall-configure-main-settings/).
- A [workload](/en/documentation/platform/workloads/) serving your application and bound to that firewall. The binding sits on the workload's deployment.
- Turning on a product or a module can generate usage costs. For the metrics Bot Manager is billed on, refer to [Pricing](/en/documentation/fundamentals/pricing/#bot-manager).

**Console**

- Access to Azion Console. To sign in, refer to [How to access Azion Console](/en/documentation/guides/platform/account-and-billing/how-to-access-azion-console/).

**CLI**

- The [Azion CLI](/en/documentation/devtools/cli/) installed and authorized.

**API**

- A personal token and `curl`. To create a token, refer to [Personal Tokens](/en/documentation/fundamentals/personal-tokens/).

---

## Create a Bot Manager Lite instance on your firewall

A function instance carries its whole configuration in one JSON object. Four arguments are enough for a first run:

```json
{
  "threshold": 30,
  "action": "deny",
  "internal_logs": 2,
  "log_tag": "storefront-bots"
}
```

`threshold` and `action` are the pair that decides the outcome: the function applies `action` to a request whose score reaches `threshold`, and `30` with `deny` are the values Bot Manager Lite ships. `internal_logs` at `2` writes a report line for every request, including one that scores `0`. `log_tag` identifies this instance in those lines, so replace `storefront-bots` with a tag of your own. For every argument an instance accepts, refer to [Arguments](/en/documentation/platform/firewall/bot-manager/arguments/#fields).

> **Caution**
>
> Nothing validates the object. Every key you send is stored and read back unchanged, whether or not the function reads it. A misspelled argument such as `thresold: 5` is kept, leaves the threshold at `30`, and raises no error in any interface.

**Console**

To create the instance in Azion Console:

1. **Open the firewall bound to your workload**

   Access [Azion Console](https://console.azion.com/) > **Firewalls**, then select that firewall.

2. **Select the Functions Instances tab**

3. **Select + Function**

   On a firewall that carries no instance yet, the same action reads **+ Function Instance**.

4. **Name the instance**

   In the **General** section, enter a **Name**. For example: `bot-manager-lite`.

5. **Select the installed function**

   In the **Function** section, select the Bot Manager Lite function. The selector lists only the functions in the account that run on a firewall.

6. **Enter the arguments**

   In the **Arguments** section, enter the object. Bot Manager Lite carries no argument schema, so the section holds a JSON editor and builds no form from it.

7. **Save the instance**

The instance appears under **Functions Instances**, which lists its **Name**, **Function**, **Last Editor**, and **Last Modified**. The form carries no **Active** control and the list carries no **Status** column: Azion Console creates every instance active.

**CLI**

`azion create firewall-instance` reads the arguments from a file. Its `--args` flag takes a path, not inline JSON.

1. **Write the arguments to a file**

   Save the object as `bmargs.json`:

   ```json
   {
     "threshold": 30,
     "action": "deny",
     "internal_logs": 2,
     "log_tag": "storefront-bots"
   }
   ```

2. **Create the instance**

   Replace `<firewall-id>` with the id of your firewall, and `<function-id>` with the id of the installed Bot Manager Lite function:

   ```bash
   azion create firewall-instance --name bot-manager-lite --firewall-id <firewall-id> \
     --function-id <function-id> --args bmargs.json --active true
   ```

3. **Read the output**

   The command prints the id of the new instance:

   ```text
   Created Firewall Function Instance with ID 12347
   ```

Record that id. The rule in the next stage names the instance by it, never by the id of the function.

**API**

The create call carries the name, the id of the function, and the arguments in one body.

1. **Send the create request**

   Replace `<firewall-id>` with the id of your firewall, `[TOKEN VALUE]` with your personal token, and `12345` with the id of the installed Bot Manager Lite function:

   ```bash
   curl --request POST \
     --url https://api.azion.com/v4/workspace/firewalls/<firewall-id>/functions \
     --header 'Accept: application/json' \
     --header 'Authorization: Token [TOKEN VALUE]' \
     --header 'Content-Type: application/json' \
     --data '{
     "name": "bot-manager-lite",
     "function": 12345,
     "active": true,
     "args": {
       "threshold": 30,
       "action": "deny",
       "internal_logs": 2,
       "log_tag": "storefront-bots"
     }
   }'
   ```

2. **Read the response**

   A create answers `202`, and `"state": "pending"` means the change is still propagating:

   ```json
   {
     "state": "pending",
     "data": {
       "id": 12348,
       "name": "bot-manager-lite",
       "args": {
         "threshold": 30,
         "action": "deny",
         "internal_logs": 2,
         "log_tag": "storefront-bots"
       },
       "azion_form": {},
       "function": 12345,
       "active": true
     }
   }
   ```

Record the `id` of the instance, `12348` in the response above. The rule in the next stage names that id, never the id of the function.

---

## Run the instance from a Rules Engine rule

A [Rules Engine for Firewall](/en/documentation/platform/firewall/rules-engine/) rule decides which requests reach the instance. Its `run_function` behavior names one instance, and the rule below runs yours on every request the firewall receives.

> **Caution**
>
> Key the criterion on the request URI. A criterion such as `${request_args}` `matches` `.*` does not match a request that carries no query string, so it skips every `POST` whose payload sits in the body.

**Console**

To create the rule in Azion Console:

1. **Open the Rules Engine tab**

   In Azion Console, go to **Firewalls**, select your firewall, then select the **Rules Engine** tab.

2. **Select + Rule**

3. **Name the rule**

   Enter a name for the rule. For example: `Run Bot Manager on every request`. The description is optional.

4. **Set the criterion**

   In the **Criteria** section, select the `Request Uri` variable, the *starts with* operator, and `/` as the argument.

5. **Add the Run Function behavior**

   In the **Behaviors** section, select **Run Function**. A second control appears, carrying the placeholder `Select an function`: it lists the function instances on this firewall, so select the one you named.

6. **Save the rule**

The firewall runs the instance on every request it receives. A rule carries at most one **Run Function** behavior.

**CLI**

`azion create firewall-rule` reads the whole rule from a JSON file. It has no flag for a criterion or a behavior.

1. **Write the rule to a file**

   Save the following as `rule.json`, with the id of your instance in `value`:

   ```json
   {
     "name": "Run Bot Manager on every request",
     "active": true,
     "criteria": [
       [
         {
           "conditional": "if",
           "variable": "${request_uri}",
           "operator": "starts_with",
           "argument": "/"
         }
       ]
     ],
     "behaviors": [
       {
         "type": "run_function",
         "attributes": {
           "value": 12347
         }
       }
     ]
   }
   ```

2. **Create the rule**

   Replace `<firewall-id>` with the id of your firewall:

   ```bash
   azion create firewall-rule --firewall-id <firewall-id> --file rule.json
   ```

3. **Read the output**

   The command prints the id of the new rule:

   ```text
   Created Firewall Rule with ID 123458
   ```

The firewall runs the instance on every request it receives. The behavior key is `type`: a file that writes `name` instead is refused with `Failed to decode the given 'json' file`, which names neither the field nor the reason.

**API**

The criteria carry `${request_uri}`, so the body is sent from a file.

1. **Write the rule to a file**

   Save the following as `rule.json`, with the id of your instance in `value`:

   ```json
   {
     "name": "Run Bot Manager on every request",
     "active": true,
     "criteria": [
       [
         {
           "conditional": "if",
           "variable": "${request_uri}",
           "operator": "starts_with",
           "argument": "/"
         }
       ]
     ],
     "behaviors": [
       {
         "type": "run_function",
         "attributes": {
           "value": 12348
         }
       }
     ]
   }
   ```

2. **Send the create request**

   Replace `<firewall-id>` with the id of your firewall:

   ```bash
   curl --request POST \
     --url https://api.azion.com/v4/workspace/firewalls/<firewall-id>/request_rules \
     --header 'Accept: application/json' \
     --header 'Authorization: Token [TOKEN VALUE]' \
     --header 'Content-Type: application/json' \
     --data @rule.json
   ```

3. **Read the response**

   The call answers `202`. The response echoes the rule and adds the `order` the platform assigns it, which is `0` for the first rule on the firewall.

The firewall runs the instance on every request it receives. The behavior key is `type`, and `attributes.value` holds the id of the instance, never the id of the function.

---

## Verify that a request is scored

The check is the same whichever interface built the instance. Your workload answers on a domain of the form `<id>.map.azionedge.net`, written below as `<your-workload-domain>`.

A new instance and a new rule take time to reach Azion's distributed infrastructure. A change to an instance reaches the request path in about two minutes. Wait before you read anything into a response.

Verify by reading the report log, not by trying to get refused. At the threshold of `30` this guide sets, a bot-shaped request is scored and still served, so the log is where the result is.

Send a request with no user agent, which is what a scripted client sends:

```bash
curl -A "" https://<your-workload-domain>/
```

Then read the line the function wrote. The report log is served by the `functionConsoleEvents` dataset. Send the query below to `https://api.azion.com/v4/events/graphql` with an `Authorization: Token [TOKEN VALUE]` header, and a `tsRange` covering the moment of the request:

```graphql
{
  functionConsoleEvents(
    limit: 200
    filter: { tsRange: { begin: "2026-01-01T11:30:00", end: "2026-01-01T13:00:00" } }
    orderBy: [ts_ASC]
  ) {
    ts
    line
    level
    lineSource
    functionId
    configurationId
  }
}
```

The query answers `200` and returns one record per line the function wrote. `functionId` is the id of the installed function, and `configurationId` is the id of the workload the request arrived on, not the id of the firewall. `line` carries the whole report line, which opens with the prefix and continues as one JSON object:

```text
[Bot-Protection][storefront-bots] Report:  {"request_id":"0123456789abcdef0123456789abcdef","remote_addr":"203.0.113.42","fingerprint":"ge20cn020000_000000000000_000000000000_000000000000","host":"<your-workload-domain>","http_user_agent":"","request_uri":"/","geoip_country":"BR","geoip_region":"SP","asn":"64496","score":28,"bot_category":"Bad Bot Signatures, Malicious Intent detected","classified":"legitimate","action":"allow","matched_rules":[1,10,18,19,20]}
```

The second bracket of the prefix carries the `log_tag` you set, which is how you tell one instance from another. Four values in the object answer the question this guide asked. `score` is `28`. `matched_rules` is `[1, 10, 18, 19, 20]`, the rules that produced that score, rule `1` among them for the empty user agent. `action` reads `allow`, and `classified` reads `legitimate`. For every field a line carries, refer to [Logs](/en/documentation/platform/firewall/bot-manager/logs/#fields).

Nothing was refused, and that is the result to expect: `28` is below the threshold of `30`, so the score never reached the value at which `deny` fires. The request was inspected, scored, and served, which is what you set out to prove.

What a lower threshold changes is the action, not the score. A request whose score reaches the threshold has `action` applied to it, and `deny` answers `HTTP 403` with Azion's default error page. The classification moves with the threshold as well: `classified` is a verdict relative to the threshold in force, so the same score of `28`, from the same matched rules, reads `legitimate` under a threshold of `30` and `bad bot` under a threshold that `28` reaches. Learn what your own traffic scores before you lower it.

---

## Next steps

- [Bot scoring](/en/documentation/platform/firewall/bot-manager/bot-scoring.md): The path a request travels, how a score is built from the rules it matches, and what the session cookies do.
- [Arguments](/en/documentation/platform/firewall/bot-manager/arguments.md): Every argument an instance accepts, with its type, its default, and the values it takes.
- [Firewall best practices](/en/documentation/platform/firewall/best-practices.md#bot-manager): How to run an observation window before a threshold refuses anything, and what each practice costs.
- [Troubleshoot Firewall](/en/documentation/platform/firewall/troubleshooting.md#bot-manager): What to do when a client is answered in a way you did not expect, or a report line you expect is missing.
