Skip to main content

Advanced rate-limiting rules

Create a rate-limiting Advanced Rule on Custom Rules to cap how many matching requests a domain accepts in a time window. Start from a Quick Template, configure request.limit_rate in the visual builder, or write a CEL expression.

A Rate Limit rule type sets a single per-IP threshold without CEL. Use Advanced Rule to apply a template, combine conditions, or edit request.limit_rate parameters.

Advanced Rule creation

  1. 1

    Custom Rules page

    In the CDB Technical Web Portal, navigate to WAAP > Custom Rules. Click Add custom rule.

  2. 2

    Advanced Rule type

    Enter a Rule name. Under Rule type, select Advanced Rule.

    Rule name Rate limit 1 with Advanced Rule selected

Quick templates

Under Quick Templates, click Captcha Rate Limit 100 req/5s or Block Rate Limit 200 req/5s.

Captcha Rate Limit 100 req/5s sets the action to Captcha and generates:

request.limit_rate(url='.*', interval=5, requests=100, scope='ip')

Block Rate Limit 200 req/5s sets the action to Block and generates:

request.limit_rate(url='.*', interval=5, requests=200, scope='ip') || tags.exists('penalty')

tags.exists('penalty') matches requests from an IP that already carries the penalty tag. The block still applies after the request rate falls below the threshold.

Advanced Rule builder with Captcha action, limit_rate condition, and Quick Templates

Both templates count requests per IP on every URL (.*). Click Save to keep the template values, or adjust the function first.

Rate-limit configuration

In the visual builder, the condition uses object request and attribute limit_rate. Click Edit Config to change the threshold, time window, URL pattern, scope, and optional filters.

request.limit_rate configuration panel with url, interval, requests, and scope

The dialog requires a URL pattern, interval, request count (minimum 20), and scope (ip or cluster). Optional lists narrow the count to specific IPs, methods, status codes, a content type, or a tag.

Click Apply Configuration. The Expression field updates to the generated CEL. Add more And rows to combine the rate limit with other attributes. Click Save.

CEL expression

Click Edit manually to replace the visual builder with a CEL editor. The generated request.limit_rate(...) expression stays in the field and can be combined with other conditions.

CEL expression editor showing request.limit_rate with Visual builder toggle

Click Visual builder to return to the condition rows. Additional conditions in the same rule control when the action runs — they do not change how requests are counted. Click Save.

Rate-limiting rules use the request.limit_rate method in a CEL source expression. This is part of WAAP advanced rules.

Rate-limiting conditions are evaluated as part of a rule's condition logic and can be combined with other request attributes to control when an action is triggered.

Rate limiting method

Use the request.limit_rate method to implement rate limiting:

limit_rate fixed method signature

request.limit_rate(
    requests = <int>,
    interval = <int>,
    url = <string>,
    ip_list = [<string>, ...],
    method_list = [<string>, ...],
    status_list = [<int>, ...],
    content_type = <string>,
    scope = <string>,
    tag = <string>
)

Specify the tag parameter when configuring tag-based rate limiting rules.

The method returns true, and the enclosing rule condition is satisfied when the request count (4), under the granularity (8), exceeds the configured threshold within the specified time window (3), using the configured filters (1, 2, 5, 6, 7, 9).

#Parameter nameRequiredDescription
1ip_listFalseList of IP addresses that the rule applies to. If there are no IPs in the list, counting will be done for all IPs.
2urlTrueA regex pattern used to match the request path, excluding query parameters.
3intervalTrueThe time window, in seconds, during which the configured number of requests is allowed.
4requestsTrueThe maximum number of requests accepted within the given period before an action is taken (minimum value is 20 requests).
5method_listFalseList of method types the request aggregation will be applied to.
6status_listFalseList of status codes the request aggregation will be applied to.
7content_typeFalseRegex pattern to match request content_type against.
8scope (granularity)TrueWhen protecting the origin, rate limit rules can apply to either IPs or all requests that come through the cluster. If set to ip, the rule is triggered when one IP exceeds the threshold — this helps against individual attackers. If set to cluster, the rule is triggered when the total requests from any IP in that cluster exceed the threshold — this helps protect the origin from getting too many requests.
9tagFalseLimits requests with the specified user-defined tag. Requests are aggregated according to scope.

Implementation

An API token is required along with the ID of a WAAP-protected domain. For SDK examples, install the Python or Go SDK. Set these as environment variables before running any of the examples:

export GCORE_API_KEY="{YOUR_API_KEY}"
export WAAP_DOMAIN_ID="{YOUR_DOMAIN_ID}"

The examples create disabled rules (enabled=false). Enable a rule after review when it is ready to take effect. To list, update, enable, disable, or delete rules, see Advanced Rules.

Rate limiting with exclusions

Rate-limit IPs that exceed 200 requests in 5 seconds on any /events path, while excluding requests from known mobile and web clients that carry session cookies.

import gcore
import os

client = gcore.CDB()
domain_id = int(os.environ["WAAP_DOMAIN_ID"])

rule = client.waap.domains.advanced_rules.create(
    domain_id,
    name="Block Scrapers",
    description="Block IPs that hit more than 200 requests per 5 seconds for any events paths",
    enabled=False,
    phase="access",
    action={"block": &#123;"status_code": 403, "action_duration": "10m"&#125;},
    source=(
        "request.limit_rate(url='.*events', interval=5, requests=200, scope='ip')"
        " and not ('mb-web-ui' in request.headers['Cookie']"
        " or 'mb-mobile-ios' in request.headers['Cookie']"
        " or 'mobile-android' in request.headers['Cookie']"
        " or 'mb-mobile-android' in request.headers['Cookie']"
        " or 'session-token' in request.headers['Cookie'])"
        " and not request.headers['session']"
    ),
)
print(f"Created rule ID: {rule.id}")
package main

import (
    "context"
    "fmt"
    "os"
    "strconv"

    gcore "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/waap"
)

func main() {
    client := gcore.NewClient()
    domainID, err := strconv.ParseInt(os.Getenv("WAAP_DOMAIN_ID"), 10, 64)
    if err != nil {
        panic(fmt.Errorf("invalid WAAP_DOMAIN_ID: %w", err))
    }

    source := "request.limit_rate(url='.*events', interval=5, requests=200, scope='ip')" +
        " and not ('mb-web-ui' in request.headers['Cookie']" +
        " or 'mb-mobile-ios' in request.headers['Cookie']" +
        " or 'mobile-android' in request.headers['Cookie']" +
        " or 'mb-mobile-android' in request.headers['Cookie']" +
        " or 'session-token' in request.headers['Cookie'])" +
        " and not request.headers['session']"

    rule, err := client.Waap.Domains.AdvancedRules.New(
        context.Background(),
        domainID,
        waap.DomainAdvancedRuleNewParams{
            Name:        "Block Scrapers",
            Description: gcore.String("Block IPs that hit more than 200 requests per 5 seconds for any events paths"),
            Enabled:     false,
            Phase:       waap.DomainAdvancedRuleNewParamsPhaseAccess,
            Action: waap.DomainAdvancedRuleNewParamsAction{
                Block: waap.DomainAdvancedRuleNewParamsActionBlock{
                    StatusCode:     403,
                    ActionDuration: gcore.String("10m"),
                },
            },
            Source: source,
        },
    )
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created rule ID: %d\n", rule.ID)
}
curl -X POST "https://api.cdb-staging.cdn.orange.com/waap/v1/domains/${WAAP_DOMAIN_ID}/advanced-rules" \
  -H "Authorization: APIKey ${GCORE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Block Scrapers",
    "description": "Block IPs that hit more than 200 requests per 5 seconds for any events paths",
    "enabled": false,
    "phase": "access",
    "action": {"block": &#123;"status_code": 403, "action_duration": "10m"&#125;},
    "source": "request.limit_rate(url='\''.*events'\'', interval=5, requests=200, scope='\''ip'\'') and not ('\''mb-web-ui'\'' in request.headers['\''Cookie'\''] or '\''mb-mobile-ios'\'' in request.headers['\''Cookie'\''] or '\''mobile-android'\'' in request.headers['\''Cookie'\''] or '\''mb-mobile-android'\'' in request.headers['\''Cookie'\''] or '\''session-token'\'' in request.headers['\''Cookie'\'']) and not request.headers['\''session'\'']"
  }'

The API returns the created rule with its assigned ID:

{
  "id": 269012,
  "name": "Block Scrapers",
  "enabled": false,
  "action": {"block": &#123;"status_code": 403, "action_duration": "10m"&#125;},
  "source": "request.limit_rate(url='.*events', interval=5, requests=200, scope='ip') ...",
  "phase": "access"
}

Rate-limiting examples

The following examples show common request.limit_rate patterns. Each example creates a disabled rule. Enable it after review. Adapt paths, IPs, and cookies to the environment.

Per-IP rate limit for any URL

Each request is counted individually per IP. When the rule is enabled, if 203.0.113.10 sends more than 200 requests within 5 seconds, it is blocked. Another IP, 203.0.113.11, is only blocked when it independently exceeds the same threshold.

import gcore
import os

client = gcore.CDB()
domain_id = int(os.environ["WAAP_DOMAIN_ID"])

rule = client.waap.domains.advanced_rules.create(
    domain_id,
    name="Limit Certain IPs",
    description="Limit Certain IPs",
    enabled=False,
    phase="access",
    action={"block": &#123;"status_code": 403&#125;},
    source="request.limit_rate(url='.*', interval=5, requests=200, scope='ip')",
)
print(f"Created rule ID: {rule.id}")
package main

import (
    "context"
    "fmt"
    "os"
    "strconv"

    gcore "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/waap"
)

func main() {
    client := gcore.NewClient()
    domainID, err := strconv.ParseInt(os.Getenv("WAAP_DOMAIN_ID"), 10, 64)
    if err != nil {
        panic(fmt.Errorf("invalid WAAP_DOMAIN_ID: %w", err))
    }

    rule, err := client.Waap.Domains.AdvancedRules.New(
        context.Background(),
        domainID,
        waap.DomainAdvancedRuleNewParams{
            Name:        "Limit Certain IPs",
            Description: gcore.String("Limit Certain IPs"),
            Enabled:     false,
            Phase:       waap.DomainAdvancedRuleNewParamsPhaseAccess,
            Action:      waap.DomainAdvancedRuleNewParamsAction{Block: waap.DomainAdvancedRuleNewParamsActionBlock{StatusCode: 403}},
            Source:      "request.limit_rate(url='.*', interval=5, requests=200, scope='ip')",
        },
    )
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created rule ID: %d\n", rule.ID)
}
curl -X POST "https://api.cdb-staging.cdn.orange.com/waap/v1/domains/${WAAP_DOMAIN_ID}/advanced-rules" \
  -H "Authorization: APIKey ${GCORE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Limit Certain IPs",
    "description": "Limit Certain IPs",
    "enabled": false,
    "phase": "access",
    "action": {"block": &#123;"status_code": 403&#125;},
    "source": "request.limit_rate(url='\''.*'\'', interval=5, requests=200, scope='\''ip'\'')"
  }'

The API returns 201 with the created rule object.

Additional conditions

Combine rate limiting with other request attributes. When the rule is enabled, the block action is applied only to Firefox user-agent requests from specific IPs that exceed the rate limit. The rate limit itself still aggregates across all configured IPs regardless of user-agent.

import gcore
import os

client = gcore.CDB()
domain_id = int(os.environ["WAAP_DOMAIN_ID"])

rule = client.waap.domains.advanced_rules.create(
    domain_id,
    name="Rate limit Firefox on events",
    description="Embedding additional condition to rate limit feature",
    enabled=False,
    phase="access",
    action={"block": &#123;"status_code": 403&#125;},
    source=(
        "request.headers['User-Agent'] == 'Firefox'"
        " and request.limit_rate(ip_list=['203.0.113.10','203.0.113.11'], url='.*events', interval=5, requests=200, scope='ip')"
    ),
)
print(f"Created rule ID: {rule.id}")
package main

import (
    "context"
    "fmt"
    "os"
    "strconv"

    gcore "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/waap"
)

func main() {
    client := gcore.NewClient()
    domainID, err := strconv.ParseInt(os.Getenv("WAAP_DOMAIN_ID"), 10, 64)
    if err != nil {
        panic(fmt.Errorf("invalid WAAP_DOMAIN_ID: %w", err))
    }

    rule, err := client.Waap.Domains.AdvancedRules.New(
        context.Background(),
        domainID,
        waap.DomainAdvancedRuleNewParams{
            Name:        "Rate limit Firefox on events",
            Description: gcore.String("Embedding additional condition to rate limit feature"),
            Enabled:     false,
            Phase:       waap.DomainAdvancedRuleNewParamsPhaseAccess,
            Action:      waap.DomainAdvancedRuleNewParamsAction{Block: waap.DomainAdvancedRuleNewParamsActionBlock{StatusCode: 403}},
            Source:      "request.headers['User-Agent'] == 'Firefox' and request.limit_rate(ip_list=['203.0.113.10','203.0.113.11'], url='.*events', interval=5, requests=200, scope='ip')",
        },
    )
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created rule ID: %d\n", rule.ID)
}
curl -X POST "https://api.cdb-staging.cdn.orange.com/waap/v1/domains/${WAAP_DOMAIN_ID}/advanced-rules" \
  -H "Authorization: APIKey ${GCORE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Rate limit Firefox on events",
    "description": "Embedding additional condition to rate limit feature",
    "enabled": false,
    "phase": "access",
    "action": {"block": &#123;"status_code": 403&#125;},
    "source": "request.headers['\''User-Agent'\''] == '\''Firefox'\'' and request.limit_rate(ip_list=['\''203.0.113.10'\'','\''203.0.113.11'\''], url='\''.*events'\'', interval=5, requests=200, scope='\''ip'\'')"
  }'

The API returns 201 with the created rule object.

Complex URL regex

Block requests to any URL that does not end in a known static asset extension — images, fonts, scripts, archives. When the rule is enabled, requests to dynamic paths are rate-limited at 20 per 120 seconds per IP.

import gcore
import os

client = gcore.CDB()
domain_id = int(os.environ["WAAP_DOMAIN_ID"])

url_pattern = (
    r".*(?<!aif|aiff|au|avi|bin|bmp|cab|carb|cct|cdf|class|css|doc|dor|dtd"
    r"|exe|flv|gcf|gff|gif|grv|hdmt|hqx|ico|ini|jpeg|jpg|js|mov|mp3|nc|pct"
    r"|pdf|png|ppc|pws|svg|swa|swf|txt|vbs|w32|wav|wbmp|wml|wmlc|wmls|wmlsc"
    r"|xsd|zip|webp|jxr|hdp|wdp|webm|ogv|mp4|tif|woff|wot)$"
)

rule = client.waap.domains.advanced_rules.create(
    domain_id,
    name="Rate limit dynamic paths",
    description="Rate limit complex URL regex",
    enabled=False,
    phase="access",
    action={"block": &#123;"status_code": 403&#125;},
    source=f"request.limit_rate(url='&#123;url_pattern&#125;', interval=120, requests=20, scope='ip')",
)
print(f"Created rule ID: {rule.id}")
package main

import (
    "context"
    "fmt"
    "os"
    "strconv"

    gcore "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/waap"
)

func main() {
    client := gcore.NewClient()
    domainID, err := strconv.ParseInt(os.Getenv("WAAP_DOMAIN_ID"), 10, 64)
    if err != nil {
        panic(fmt.Errorf("invalid WAAP_DOMAIN_ID: %w", err))
    }

    urlPattern := `.*(?<!aif|aiff|au|avi|bin|bmp|cab|carb|cct|cdf|class|css|doc|dor|dtd|exe|flv|gcf|gff|gif|grv|hdmt|hqx|ico|ini|jpeg|jpg|js|mov|mp3|nc|pct|pdf|png|ppc|pws|svg|swa|swf|txt|vbs|w32|wav|wbmp|wml|wmlc|wmls|wmlsc|xsd|zip|webp|jxr|hdp|wdp|webm|ogv|mp4|tif|woff|wot)$`
    source := "request.limit_rate(url='" + urlPattern + "', interval=120, requests=20, scope='ip')"

    rule, err := client.Waap.Domains.AdvancedRules.New(
        context.Background(),
        domainID,
        waap.DomainAdvancedRuleNewParams{
            Name:        "Rate limit dynamic paths",
            Description: gcore.String("Rate limit complex URL regex"),
            Enabled:     false,
            Phase:       waap.DomainAdvancedRuleNewParamsPhaseAccess,
            Action:      waap.DomainAdvancedRuleNewParamsAction{Block: waap.DomainAdvancedRuleNewParamsActionBlock{StatusCode: 403}},
            Source:      source,
        },
    )
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created rule ID: %d\n", rule.ID)
}
curl -X POST "https://api.cdb-staging.cdn.orange.com/waap/v1/domains/${WAAP_DOMAIN_ID}/advanced-rules" \
  -H "Authorization: APIKey ${GCORE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Rate limit dynamic paths",
    "description": "Rate limit complex URL regex",
    "enabled": false,
    "phase": "access",
    "action": {"block": &#123;"status_code": 403&#125;},
    "source": "request.limit_rate(url='\''.*(?<!aif|aiff|au|avi|bin|bmp|cab|carb|cct|cdf|class|css|doc|dor|dtd|exe|flv|gcf|gff|gif|grv|hdmt|hqx|ico|ini|jpeg|jpg|js|mov|mp3|nc|pct|pdf|png|ppc|pws|svg|swa|swf|txt|vbs|w32|wav|wbmp|wml|wmlc|wmls|wmlsc|xsd|zip|webp|jxr|hdp|wdp|webm|ogv|mp4|tif|woff|wot)$'\'', interval=120, requests=20, scope='\''ip'\'')"
  }'

The API returns 201 with the created rule object.

IP range condition

Rate-limit JPEG requests from a documentation IPv4 range. The request.ip_in_range condition acts as a filter — the rate limit aggregation still counts per IP within the range.

import gcore
import os

client = gcore.CDB()
domain_id = int(os.environ["WAAP_DOMAIN_ID"])

rule = client.waap.domains.advanced_rules.create(
    domain_id,
    name="Rate limit IP range on jpg",
    description="Embedding IP range to the condition",
    enabled=False,
    phase="access",
    action={"block": &#123;"status_code": 403&#125;},
    source="request.ip_in_range('192.0.2.0', '192.0.2.255') and request.limit_rate(url='.*[.]jpg', interval=120, requests=20, scope='ip')",
)
print(f"Created rule ID: {rule.id}")
package main

import (
    "context"
    "fmt"
    "os"
    "strconv"

    gcore "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/waap"
)

func main() {
    client := gcore.NewClient()
    domainID, err := strconv.ParseInt(os.Getenv("WAAP_DOMAIN_ID"), 10, 64)
    if err != nil {
        panic(fmt.Errorf("invalid WAAP_DOMAIN_ID: %w", err))
    }

    rule, err := client.Waap.Domains.AdvancedRules.New(
        context.Background(),
        domainID,
        waap.DomainAdvancedRuleNewParams{
            Name:        "Rate limit IP range on jpg",
            Description: gcore.String("Embedding IP range to the condition"),
            Enabled:     false,
            Phase:       waap.DomainAdvancedRuleNewParamsPhaseAccess,
            Action:      waap.DomainAdvancedRuleNewParamsAction{Block: waap.DomainAdvancedRuleNewParamsActionBlock{StatusCode: 403}},
            Source:      "request.ip_in_range('192.0.2.0', '192.0.2.255') and request.limit_rate(url='.*[.]jpg', interval=120, requests=20, scope='ip')",
        },
    )
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created rule ID: %d\n", rule.ID)
}
curl -X POST "https://api.cdb-staging.cdn.orange.com/waap/v1/domains/${WAAP_DOMAIN_ID}/advanced-rules" \
  -H "Authorization: APIKey ${GCORE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Rate limit IP range on jpg",
    "description": "Embedding IP range to the condition",
    "enabled": false,
    "phase": "access",
    "action": {"block": &#123;"status_code": 403&#125;},
    "source": "request.ip_in_range('\''192.0.2.0'\'', '\''192.0.2.255'\'') and request.limit_rate(url='\''.*[.]jpg'\'', interval=120, requests=20, scope='\''ip'\'')"
  }'

The API returns 201 with the created rule object.

Cluster granularity

Rate-limit all GET or HEAD requests that return a 302 redirect with a specific content type. The cluster scope counts requests across IPs within the same cluster or point of presence (PoP), not necessarily across the entire WAAP network — useful for protecting origin from aggregate traffic spikes on that PoP.

import gcore
import os

client = gcore.CDB()
domain_id = int(os.environ["WAAP_DOMAIN_ID"])

rule = client.waap.domains.advanced_rules.create(
    domain_id,
    name="RL GET or HEAD redirected requests cluster",
    description="Rate limit all GET or HEAD redirected requests with specific content type on Cluster granularity",
    enabled=False,
    phase="access",
    action={"block": &#123;"status_code": 403&#125;},
    source="request.limit_rate(url='.*url', interval=120, requests=20, method_list=['GET','HEAD'], status_list=[302], content_type='text/html; charset=utf-8', scope='cluster')",
)
print(f"Created rule ID: {rule.id}")
package main

import (
    "context"
    "fmt"
    "os"
    "strconv"

    gcore "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/waap"
)

func main() {
    client := gcore.NewClient()
    domainID, err := strconv.ParseInt(os.Getenv("WAAP_DOMAIN_ID"), 10, 64)
    if err != nil {
        panic(fmt.Errorf("invalid WAAP_DOMAIN_ID: %w", err))
    }

    rule, err := client.Waap.Domains.AdvancedRules.New(
        context.Background(),
        domainID,
        waap.DomainAdvancedRuleNewParams{
            Name:        "RL GET or HEAD redirected requests cluster",
            Description: gcore.String("Rate limit all GET or HEAD redirected requests with specific content type on Cluster granularity"),
            Enabled:     false,
            Phase:       waap.DomainAdvancedRuleNewParamsPhaseAccess,
            Action:      waap.DomainAdvancedRuleNewParamsAction{Block: waap.DomainAdvancedRuleNewParamsActionBlock{StatusCode: 403}},
            Source:      "request.limit_rate(url='.*url', interval=120, requests=20, method_list=['GET','HEAD'], status_list=[302], content_type='text/html; charset=utf-8', scope='cluster')",
        },
    )
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created rule ID: %d\n", rule.ID)
}
curl -X POST "https://api.cdb-staging.cdn.orange.com/waap/v1/domains/${WAAP_DOMAIN_ID}/advanced-rules" \
  -H "Authorization: APIKey ${GCORE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "RL GET or HEAD redirected requests cluster",
    "description": "Rate limit all GET or HEAD redirected requests with specific content type on Cluster granularity",
    "enabled": false,
    "phase": "access",
    "action": {"block": &#123;"status_code": 403&#125;},
    "source": "request.limit_rate(url='\''.*url'\'', interval=120, requests=20, method_list=['\''GET'\'','\''HEAD'\''], status_list=[302], content_type='\''text/html; charset=utf-8'\'', scope='\''cluster'\'')"
  }'

The API returns 201 with the created rule object.

Tag filter

Apply rate limiting only to requests that carry a specific user-defined tag. The tag must be assigned by a separate tag rule that runs before this one. This allows targeting specific sessions or clients identified by earlier rules.

import gcore
import os

client = gcore.CDB()
domain_id = int(os.environ["WAAP_DOMAIN_ID"])

rule = client.waap.domains.advanced_rules.create(
    domain_id,
    name="Rate limit by tag filter",
    description="Embedding tag to the condition",
    enabled=False,
    phase="access",
    action={"block": &#123;"status_code": 403&#125;},
    source="request.limit_rate(tag='my tag', ip_list=['198.51.100.10'], url='/my_url/.*', interval=10, requests=120, scope='ip')",
)
print(f"Created rule ID: {rule.id}")
package main

import (
    "context"
    "fmt"
    "os"
    "strconv"

    gcore "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/waap"
)

func main() {
    client := gcore.NewClient()
    domainID, err := strconv.ParseInt(os.Getenv("WAAP_DOMAIN_ID"), 10, 64)
    if err != nil {
        panic(fmt.Errorf("invalid WAAP_DOMAIN_ID: %w", err))
    }

    rule, err := client.Waap.Domains.AdvancedRules.New(
        context.Background(),
        domainID,
        waap.DomainAdvancedRuleNewParams{
            Name:        "Rate limit by tag filter",
            Description: gcore.String("Embedding tag to the condition"),
            Enabled:     false,
            Phase:       waap.DomainAdvancedRuleNewParamsPhaseAccess,
            Action:      waap.DomainAdvancedRuleNewParamsAction{Block: waap.DomainAdvancedRuleNewParamsActionBlock{StatusCode: 403}},
            Source:      "request.limit_rate(tag='my tag', ip_list=['198.51.100.10'], url='/my_url/.*', interval=10, requests=120, scope='ip')",
        },
    )
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created rule ID: %d\n", rule.ID)
}
curl -X POST "https://api.cdb-staging.cdn.orange.com/waap/v1/domains/${WAAP_DOMAIN_ID}/advanced-rules" \
  -H "Authorization: APIKey ${GCORE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Rate limit by tag filter",
    "description": "Embedding tag to the condition",
    "enabled": false,
    "phase": "access",
    "action": {"block": &#123;"status_code": 403&#125;},
    "source": "request.limit_rate(tag='\''my tag'\'', ip_list=['\''198.51.100.10'\''], url='\''/my_url/.*'\'', interval=10, requests=120, scope='\''ip'\'')"
  }'

The API returns 201 with the created rule object.

Rate limit aggregation and rule triggering

Rate limit aggregation is defined exclusively by the parameters of the request.limit_rate(...) condition, such as the aggregation scope, IP list, or tags.

When request.limit_rate(...) is combined with additional conditions in the same rule, those additional conditions control when the rule action is applied, not how requests are aggregated.

For example, if a rate limit condition is configured without an IP list and combined with a condition such as request.ip == '203.0.113.10', request aggregation will still occur according to the rate limit definition. However, the rule action will only be triggered when the full rule condition evaluates to true — in this case, only for requests originating from 203.0.113.10.

This distinction is important when combining rate limiting with other conditions so the rule behaves as expected.