From 8b0ef87b60dcb15fc71e839069355c3afaccd3dd Mon Sep 17 00:00:00 2001 From: "oleksiy.rykhalskyy" Date: Fri, 20 Feb 2026 16:50:52 +0200 Subject: [PATCH 01/12] draft --- docs/storefront/graphql/limiter.mdx | 57 +++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 docs/storefront/graphql/limiter.mdx diff --git a/docs/storefront/graphql/limiter.mdx b/docs/storefront/graphql/limiter.mdx new file mode 100644 index 000000000..0d3f71296 --- /dev/null +++ b/docs/storefront/graphql/limiter.mdx @@ -0,0 +1,57 @@ +--- +title: GraphQL Storefront API +keywords: graphql, storefront, limiter +--- + +# GraphQL Complexity Limiter + +--- + +A. We have basically 3 types of clients: + +1. Stencil (pure frontend) + +When you are using Stencil, you are basically a frontend client, +and you need to be careful with the number of queries you are sending on page load. +You should optimize your queries, use caching for repeating queries. + +Try not to send bunch of graphql queries on page load, only what you need, and cache the rest. + +2. Catalyst (any headless) + +When you are using Catalyst, you can be either a frontend or a server client, +depending on your architecture. + +Try to use as minimum GraphQL queries as possible, and cache responses when you can. + +3. Server-to-server + +When you are doing server-to-server, you have more control over your queries, +and you can do more optimizations. + +Use a trusted proxy to propagate ip address and cache responses at the edge.) + +--- + +B. Since amount of traffic is huge, you should be careful with the number of queries +you are sending, and try to optimize them as much as possible. + +C. We have a limiter in place to protect our infrastructure, +and if you exceed the limits, you will get HTTP 429 errors. + +- Limiter is based on a token bucket algorithm, +and you have a certain number of tokens that you can use simultaneously. + +- Limiter is applied per store, and it is based on the number of concurrent queries you are sending. + +- Limiter is applied per IP address, so you don't need to be worry about real users, +each of them is using corresponding counter on our side. + +- We have a monitoring system in place, and when your store overreaches the limit, +we will see it in our monitoring system, and you will be notified about it. + +- There are certain number of headers added to the response to understand +the state of the limiter, and you can use them to optimize your implementation. + + - `X-BC-IP-Rate-Limit-Requests-Quota`: + - `X-BC-IP-Rate-Limit-Requests-Quota-Left`: From 38a9fa7a53f74a7d922485c714f8565afd99ebde Mon Sep 17 00:00:00 2001 From: "oleksiy.rykhalskyy" Date: Fri, 20 Feb 2026 17:13:30 +0200 Subject: [PATCH 02/12] formatting --- docs/storefront/graphql/limiter.mdx | 33 ++++++++++++----------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/docs/storefront/graphql/limiter.mdx b/docs/storefront/graphql/limiter.mdx index 0d3f71296..602f76125 100644 --- a/docs/storefront/graphql/limiter.mdx +++ b/docs/storefront/graphql/limiter.mdx @@ -1,15 +1,10 @@ ---- -title: GraphQL Storefront API -keywords: graphql, storefront, limiter ---- - # GraphQL Complexity Limiter --- -A. We have basically 3 types of clients: +## We have basically 3 types of clients: -1. Stencil (pure frontend) +1. **Stencil (pure frontend)** When you are using Stencil, you are basically a frontend client, and you need to be careful with the number of queries you are sending on page load. @@ -17,14 +12,14 @@ You should optimize your queries, use caching for repeating queries. Try not to send bunch of graphql queries on page load, only what you need, and cache the rest. -2. Catalyst (any headless) +2. **Catalyst (any headless)** When you are using Catalyst, you can be either a frontend or a server client, depending on your architecture. Try to use as minimum GraphQL queries as possible, and cache responses when you can. -3. Server-to-server +3. **Server-to-server** When you are doing server-to-server, you have more control over your queries, and you can do more optimizations. @@ -33,25 +28,23 @@ Use a trusted proxy to propagate ip address and cache responses at the edge.) --- -B. Since amount of traffic is huge, you should be careful with the number of queries -you are sending, and try to optimize them as much as possible. +## Since amount of traffic is huge, you should be careful with the number of queries you are sending, and try to optimize them as much as possible. -C. We have a limiter in place to protect our infrastructure, -and if you exceed the limits, you will get HTTP 429 errors. +## We have a limiter in place to protect our infrastructure, and if you exceed the limits, you will get HTTP 429 errors. -- Limiter is based on a token bucket algorithm, +* Limiter is based on a token bucket algorithm, and you have a certain number of tokens that you can use simultaneously. -- Limiter is applied per store, and it is based on the number of concurrent queries you are sending. +* Limiter is applied per store, and it is based on the number of concurrent queries you are sending. -- Limiter is applied per IP address, so you don't need to be worry about real users, +* Limiter is applied per IP address, so you don't need to be worry about real users, each of them is using corresponding counter on our side. -- We have a monitoring system in place, and when your store overreaches the limit, +* We have a monitoring system in place, and when your store overreaches the limit, we will see it in our monitoring system, and you will be notified about it. -- There are certain number of headers added to the response to understand +* There are certain number of headers added to the response to understand the state of the limiter, and you can use them to optimize your implementation. - - `X-BC-IP-Rate-Limit-Requests-Quota`: - - `X-BC-IP-Rate-Limit-Requests-Quota-Left`: + - `X-BC-IP-Rate-Limit-Requests-Quota`: numeric_value + - `X-BC-IP-Rate-Limit-Requests-Quota-Left`: numeric_value From ddc152b85b625bbc1a74dd25c57d76c05425bd23 Mon Sep 17 00:00:00 2001 From: "oleksiy.rykhalskyy" Date: Fri, 20 Feb 2026 17:15:02 +0200 Subject: [PATCH 03/12] formatting --- docs/storefront/graphql/limiter.mdx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/storefront/graphql/limiter.mdx b/docs/storefront/graphql/limiter.mdx index 602f76125..14652e6a9 100644 --- a/docs/storefront/graphql/limiter.mdx +++ b/docs/storefront/graphql/limiter.mdx @@ -32,19 +32,19 @@ Use a trusted proxy to propagate ip address and cache responses at the edge.) ## We have a limiter in place to protect our infrastructure, and if you exceed the limits, you will get HTTP 429 errors. -* Limiter is based on a token bucket algorithm, +- Limiter is based on a token bucket algorithm, and you have a certain number of tokens that you can use simultaneously. -* Limiter is applied per store, and it is based on the number of concurrent queries you are sending. +- Limiter is applied per store, and it is based on the number of concurrent queries you are sending. -* Limiter is applied per IP address, so you don't need to be worry about real users, +- Limiter is applied per IP address, so you don't need to be worry about real users, each of them is using corresponding counter on our side. -* We have a monitoring system in place, and when your store overreaches the limit, +- We have a monitoring system in place, and when your store overreaches the limit, we will see it in our monitoring system, and you will be notified about it. -* There are certain number of headers added to the response to understand +- There are certain number of headers added to the response to understand the state of the limiter, and you can use them to optimize your implementation. - - `X-BC-IP-Rate-Limit-Requests-Quota`: numeric_value - - `X-BC-IP-Rate-Limit-Requests-Quota-Left`: numeric_value + - `X-BC-IP-Rate-Limit-Requests-Quota`: numeric_value + - `X-BC-IP-Rate-Limit-Requests-Quota-Left`: numeric_value From 7303158a8286d9abd5eb112efcd479bb9386ef19 Mon Sep 17 00:00:00 2001 From: "oleksiy.rykhalskyy" Date: Fri, 20 Feb 2026 18:28:20 +0200 Subject: [PATCH 04/12] more header --- docs/storefront/graphql/limiter.mdx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/storefront/graphql/limiter.mdx b/docs/storefront/graphql/limiter.mdx index 14652e6a9..9476a317f 100644 --- a/docs/storefront/graphql/limiter.mdx +++ b/docs/storefront/graphql/limiter.mdx @@ -45,6 +45,9 @@ we will see it in our monitoring system, and you will be notified about it. - There are certain number of headers added to the response to understand the state of the limiter, and you can use them to optimize your implementation. - - `X-BC-IP-Rate-Limit-Requests-Quota`: numeric_value - `X-BC-IP-Rate-Limit-Requests-Quota-Left`: numeric_value + +- In case of quota reached there will be additional header: + - `X-BC-Rate-Limit-Reason-Code`: `1` +and 429 status code will be returned. From d4affb94171a2d6a0e9f26f3aaf9587a0d2cc924 Mon Sep 17 00:00:00 2001 From: "oleksiy.rykhalskyy" Date: Mon, 23 Feb 2026 17:50:40 +0200 Subject: [PATCH 05/12] better --- docs/storefront/graphql/limiter.mdx | 125 +++++++++++++++++++++------- 1 file changed, 95 insertions(+), 30 deletions(-) diff --git a/docs/storefront/graphql/limiter.mdx b/docs/storefront/graphql/limiter.mdx index 9476a317f..0119dd003 100644 --- a/docs/storefront/graphql/limiter.mdx +++ b/docs/storefront/graphql/limiter.mdx @@ -1,53 +1,118 @@ # GraphQL Complexity Limiter +This document explains how the **per-IP inflight GraphQL complexity limiter** behaves in Storefront GraphQL and how to interpret limiter headers. + +--- + +## Why your query can be limited + +The limiter protects Storefront GraphQL infrastructure from bursts of expensive concurrent traffic from a single source. + +At runtime, each request has a calculated GraphQL complexity. The service tracks in-flight complexity per IP +and rejects requests when accepting a new one would exceed the configured per-IP in-flight threshold. +This prevents one source from over-consuming shared compute and helps keep latency stable for everyone. + +Important implementation details: + +- The limiter is applied only for Storefront GraphQL API origin requests. +- Counters are in-flight counters (increment before execution, decrement after execution), not daily/monthly request budgets. + +--- + +## Quota value is not a public contract + +The `X-BC-IP-Rate-Limit-Requests-Quota` header reflects the **current configured per-IP in-flight complexity threshold**. + +- It can be changed without notice. +- It should **not** be treated as a fixed, publicly advertised SLA number. + +In other words: do not hardcode logic around a specific value (for example 100k). Always read and react to headers dynamically. + --- -## We have basically 3 types of clients: +## Client profiles and recommendations -1. **Stencil (pure frontend)** +### 1) Stencil (pure frontend) -When you are using Stencil, you are basically a frontend client, -and you need to be careful with the number of queries you are sending on page load. -You should optimize your queries, use caching for repeating queries. +When using Stencil, you are a frontend client. Optimize page-load behavior: -Try not to send bunch of graphql queries on page load, only what you need, and cache the rest. +- Avoid firing many GraphQL queries at once on initial page load. +- Prefer batching/fewer round-trips where possible. +- Cache repeated reads aggressively. -2. **Catalyst (any headless)** +### 2) Catalyst (headless) -When you are using Catalyst, you can be either a frontend or a server client, -depending on your architecture. +Catalyst can act like frontend traffic or backend traffic depending on your architecture. -Try to use as minimum GraphQL queries as possible, and cache responses when you can. +- Keep GraphQL calls minimal. +- Cache reusable results. +- Control concurrency on server-render paths. -3. **Server-to-server** +### 3) Server-to-server -When you are doing server-to-server, you have more control over your queries, -and you can do more optimizations. +You usually have the most control. -Use a trusted proxy to propagate ip address and cache responses at the edge.) +- Use a trusted proxy/load balancer configuration so the effective client IP is propagated correctly. +- Add edge/application caching. +- Avoid fan-out bursts from one egress IP. --- -## Since amount of traffic is huge, you should be careful with the number of queries you are sending, and try to optimize them as much as possible. +## Header reference + +### `X-BC-Rate-Limit-Reason-Code` + +Reason code describing whether and why rate limiting occurred: + +- `0` — request not limited by rate limiter +- `1` — throttled by **per-IP inflight complexity limiter** + +### `X-BC-IP-Rate-Limit-Requests-Quota` + +Current configured per-IP in-flight complexity threshold used by limiter evaluation for this request. + +### `X-BC-IP-Rate-Limit-Requests-Quota-Left` + +Remaining per-IP in-flight complexity before crossing threshold, based on current in-flight counters. + +--- + +## All response header combinations and examples + +> Note: examples show realistic values; exact numbers vary per environment and traffic state. + +### Case A: Per-IP limiter evaluated, request allowed + +- Typical status: `200` (or any non-429 business status) +- Headers: + +```http +X-BC-Rate-Limit-Reason-Code: 0 +X-BC-IP-Rate-Limit-Requests-Quota: 80000 +X-BC-IP-Rate-Limit-Requests-Quota-Left: 23420 +``` -## We have a limiter in place to protect our infrastructure, and if you exceed the limits, you will get HTTP 429 errors. +Meaning: request passed and the limiter reports current quota and remaining headroom. -- Limiter is based on a token bucket algorithm, -and you have a certain number of tokens that you can use simultaneously. +### Case B: Per-IP limiter evaluated, request rejected -- Limiter is applied per store, and it is based on the number of concurrent queries you are sending. +- Status: `429 Too Many Requests` +- Headers: -- Limiter is applied per IP address, so you don't need to be worry about real users, -each of them is using corresponding counter on our side. +```http +X-BC-Rate-Limit-Reason-Code: 1 +X-BC-IP-Rate-Limit-Requests-Quota: 80000 +X-BC-IP-Rate-Limit-Requests-Quota-Left: 70 +``` -- We have a monitoring system in place, and when your store overreaches the limit, -we will see it in our monitoring system, and you will be notified about it. +Meaning: request was throttled because this IP exceeded per-IP in-flight complexity threshold. -- There are certain number of headers added to the response to understand -the state of the limiter, and you can use them to optimize your implementation. - - `X-BC-IP-Rate-Limit-Requests-Quota`: numeric_value - - `X-BC-IP-Rate-Limit-Requests-Quota-Left`: numeric_value +## Operational guidance -- In case of quota reached there will be additional header: - - `X-BC-Rate-Limit-Reason-Code`: `1` -and 429 status code will be returned. +- Treat `429` as retryable with backoff/jitter. +- Reduce burst concurrency per IP. +- Cache read-heavy queries. +- Avoid redundant page-load calls. +- Inspect `X-BC-Rate-Limit-Reason-Code` first to distinguish per-IP complexity throttling (`1`) vs query/basic limiter throttling (`2`). +- Inspect `X-BC-IP-Rate-Limit-Requests-Quota` for a quota configured +- Inspect `X-BC-IP-Rate-Limit-Requests-Quota-Left`: inspect whether you have enough headroom or are close to the threshold. From 892bd3ce1b20eb4e5f6172b4c8b7311fb4f7c8ff Mon Sep 17 00:00:00 2001 From: Oleksiy Rykhalskyy Date: Mon, 23 Feb 2026 18:07:07 +0200 Subject: [PATCH 06/12] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/storefront/graphql/limiter.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/storefront/graphql/limiter.mdx b/docs/storefront/graphql/limiter.mdx index 0119dd003..8eb694f74 100644 --- a/docs/storefront/graphql/limiter.mdx +++ b/docs/storefront/graphql/limiter.mdx @@ -113,6 +113,6 @@ Meaning: request was throttled because this IP exceeded per-IP in-flight complex - Reduce burst concurrency per IP. - Cache read-heavy queries. - Avoid redundant page-load calls. -- Inspect `X-BC-Rate-Limit-Reason-Code` first to distinguish per-IP complexity throttling (`1`) vs query/basic limiter throttling (`2`). -- Inspect `X-BC-IP-Rate-Limit-Requests-Quota` for a quota configured -- Inspect `X-BC-IP-Rate-Limit-Requests-Quota-Left`: inspect whether you have enough headroom or are close to the threshold. +- Inspect `X-BC-Rate-Limit-Reason-Code` first to distinguish requests not limited (`0`) from requests throttled by the per-IP inflight complexity limiter (`1`). +- Inspect `X-BC-IP-Rate-Limit-Requests-Quota` for the configured quota value. +- Inspect `X-BC-IP-Rate-Limit-Requests-Quota-Left`: to inspect whether you have enough headroom or are close to the threshold. From 2acbc7a36cf75123225f4b51998e215356cce4ee Mon Sep 17 00:00:00 2001 From: "oleksiy.rykhalskyy" Date: Mon, 23 Feb 2026 18:08:05 +0200 Subject: [PATCH 07/12] better --- docs/storefront/graphql/limiter.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/storefront/graphql/limiter.mdx b/docs/storefront/graphql/limiter.mdx index 8eb694f74..3409d299f 100644 --- a/docs/storefront/graphql/limiter.mdx +++ b/docs/storefront/graphql/limiter.mdx @@ -115,4 +115,4 @@ Meaning: request was throttled because this IP exceeded per-IP in-flight complex - Avoid redundant page-load calls. - Inspect `X-BC-Rate-Limit-Reason-Code` first to distinguish requests not limited (`0`) from requests throttled by the per-IP inflight complexity limiter (`1`). - Inspect `X-BC-IP-Rate-Limit-Requests-Quota` for the configured quota value. -- Inspect `X-BC-IP-Rate-Limit-Requests-Quota-Left`: to inspect whether you have enough headroom or are close to the threshold. +- Inspect `X-BC-IP-Rate-Limit-Requests-Quota-Left`: to understand whether you have enough headroom or are close to the threshold. From ef7b90341a0d5c7e977fc51b7e85b4103665fef2 Mon Sep 17 00:00:00 2001 From: "oleksiy.rykhalskyy" Date: Tue, 24 Feb 2026 21:26:42 +0200 Subject: [PATCH 08/12] better 3 --- docs/storefront/graphql/limiter.mdx | 54 ++++++++++++----------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/docs/storefront/graphql/limiter.mdx b/docs/storefront/graphql/limiter.mdx index 3409d299f..e91a4cdf5 100644 --- a/docs/storefront/graphql/limiter.mdx +++ b/docs/storefront/graphql/limiter.mdx @@ -12,21 +12,7 @@ At runtime, each request has a calculated GraphQL complexity. The service tracks and rejects requests when accepting a new one would exceed the configured per-IP in-flight threshold. This prevents one source from over-consuming shared compute and helps keep latency stable for everyone. -Important implementation details: - -- The limiter is applied only for Storefront GraphQL API origin requests. -- Counters are in-flight counters (increment before execution, decrement after execution), not daily/monthly request budgets. - ---- - -## Quota value is not a public contract - -The `X-BC-IP-Rate-Limit-Requests-Quota` header reflects the **current configured per-IP in-flight complexity threshold**. - -- It can be changed without notice. -- It should **not** be treated as a fixed, publicly advertised SLA number. - -In other words: do not hardcode logic around a specific value (for example 100k). Always read and react to headers dynamically. +The limiter is applied only for Storefront GraphQL API origin requests. --- @@ -60,13 +46,6 @@ You usually have the most control. ## Header reference -### `X-BC-Rate-Limit-Reason-Code` - -Reason code describing whether and why rate limiting occurred: - -- `0` — request not limited by rate limiter -- `1` — throttled by **per-IP inflight complexity limiter** - ### `X-BC-IP-Rate-Limit-Requests-Quota` Current configured per-IP in-flight complexity threshold used by limiter evaluation for this request. @@ -87,9 +66,8 @@ Remaining per-IP in-flight complexity before crossing threshold, based on curren - Headers: ```http -X-BC-Rate-Limit-Reason-Code: 0 -X-BC-IP-Rate-Limit-Requests-Quota: 80000 -X-BC-IP-Rate-Limit-Requests-Quota-Left: 23420 +X-BC-IP-Rate-Limit-Requests-Quota: 50000 +X-BC-IP-Rate-Limit-Requests-Quota-Left: 35000 ``` Meaning: request passed and the limiter reports current quota and remaining headroom. @@ -100,12 +78,22 @@ Meaning: request passed and the limiter reports current quota and remaining head - Headers: ```http -X-BC-Rate-Limit-Reason-Code: 1 -X-BC-IP-Rate-Limit-Requests-Quota: 80000 -X-BC-IP-Rate-Limit-Requests-Quota-Left: 70 +X-BC-IP-Rate-Limit-Requests-Quota: 50000 +X-BC-IP-Rate-Limit-Requests-Quota-Left: 750 ``` -Meaning: request was throttled because this IP exceeded per-IP in-flight complexity threshold. +Meaning: request was rejected because this IP exceeded per-IP in-flight complexity threshold. + +--- + +## Quota value is not a public contract + +The `X-BC-IP-Rate-Limit-Requests-Quota` header reflects the **current configured per-IP in-flight complexity threshold**. + +- It can be changed without notice. +- It should **not** be treated as a fixed, publicly advertised SLA number. + +In other words: do not hardcode logic around a specific value. Always read and react to headers dynamically. ## Operational guidance @@ -113,6 +101,8 @@ Meaning: request was throttled because this IP exceeded per-IP in-flight complex - Reduce burst concurrency per IP. - Cache read-heavy queries. - Avoid redundant page-load calls. -- Inspect `X-BC-Rate-Limit-Reason-Code` first to distinguish requests not limited (`0`) from requests throttled by the per-IP inflight complexity limiter (`1`). -- Inspect `X-BC-IP-Rate-Limit-Requests-Quota` for the configured quota value. -- Inspect `X-BC-IP-Rate-Limit-Requests-Quota-Left`: to understand whether you have enough headroom or are close to the threshold. +- Inspect `X-BC-IP-Rate-Limit-Requests-Quota` for the configured quota value (the maximum allowed request weight for the current window). +- Inspect `X-BC-IP-Rate-Limit-Requests-Quota-Left` to understand how much quota remains and whether you are approaching the threshold. +- Calculate the current request’s query complexity by subtracting `X-BC-IP-Rate-Limit-Requests-Quota-Left` from `X-BC-IP-Rate-Limit-Requests-Quota`. + The difference represents how much of the quota has been consumed in the current window, + which reflects the effective complexity of the executed query. From f5694d24a200127d2c5a6a602bf10a2d3dfcf05b Mon Sep 17 00:00:00 2001 From: Vitya Chyzhyk <33040515+6juara9@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:20:50 +0100 Subject: [PATCH 09/12] refined doc (#1268) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # [DEVDOCS-] ## What changed? * ## Release notes draft * ## Anything else? ping {names} --- docs/storefront/graphql/limiter.mdx | 112 +++++++--------------------- 1 file changed, 29 insertions(+), 83 deletions(-) diff --git a/docs/storefront/graphql/limiter.mdx b/docs/storefront/graphql/limiter.mdx index e91a4cdf5..d767583c0 100644 --- a/docs/storefront/graphql/limiter.mdx +++ b/docs/storefront/graphql/limiter.mdx @@ -1,108 +1,54 @@ -# GraphQL Complexity Limiter +# Storefront GraphQL platform protection -This document explains how the **per-IP inflight GraphQL complexity limiter** behaves in Storefront GraphQL and how to interpret limiter headers. +Storefront GraphQL is protected from over-consumption so that traffic remains fair and stable for all users. This page describes how that protection works from a client perspective. --- -## Why your query can be limited +## How protection works -The limiter protects Storefront GraphQL infrastructure from bursts of expensive concurrent traffic from a single source. +GraphQL requests are **not** rate-limited the same way as REST API endpoints. Protection is based on **how much data** each request asks for—heavier queries (more or deeper fields) consume more of the available capacity than lighter ones. If too much is requested at once from a single source, some requests may be rejected with `429 Too Many Requests` until capacity frees up. -At runtime, each request has a calculated GraphQL complexity. The service tracks in-flight complexity per IP -and rejects requests when accepting a new one would exceed the configured per-IP in-flight threshold. -This prevents one source from over-consuming shared compute and helps keep latency stable for everyone. - -The limiter is applied only for Storefront GraphQL API origin requests. +The mechanism is applied only to Storefront GraphQL; it does not affect other APIs. --- -## Client profiles and recommendations - -### 1) Stencil (pure frontend) - -When using Stencil, you are a frontend client. Optimize page-load behavior: +## Common issues and recommendations -- Avoid firing many GraphQL queries at once on initial page load. -- Prefer batching/fewer round-trips where possible. -- Cache repeated reads aggressively. +**General:** Use **lazy loading** for below-the-fold or secondary content so you don’t request everything up front. **Refine your queries** so each one asks only for the fields and depth you actually use—smaller, focused requests use less capacity and are less likely to trigger protection. -### 2) Catalyst (headless) +### Stencil -Catalyst can act like frontend traffic or backend traffic depending on your architecture. +Heavy themes that fire many GraphQL calls at once on page load (e.g. header, footer, product grid, recommendations) can hit protection limits. The same applies to custom scripts that run several queries in parallel or request more data than the page needs. -- Keep GraphQL calls minimal. -- Cache reusable results. -- Control concurrency on server-render paths. +- Avoid firing many GraphQL queries at once on initial page load; use lazy loading where possible. +- Prefer batching or fewer round-trips. -### 3) Server-to-server +### Headless -You usually have the most control. - -- Use a trusted proxy/load balancer configuration so the effective client IP is propagated correctly. -- Add edge/application caching. -- Avoid fan-out bursts from one egress IP. +Missing or weak caching is a frequent cause of unnecessary load. Most storefront data (products, categories, content) is public and should be cacheable. +- Cache reusable results and control concurrency on server-render paths. +- Keep GraphQL calls minimal and only request what you need. +- [Catalyst](https://www.catalyst.dev/) is designed to follow best practices for Storefront GraphQL and is a good default for headless builds. +- Ensure proper IP address propagation. See [this guide](https://support.bigcommerce.com/s/article/Third-Party-Reverse-Proxies?language=en_US) for details. --- -## Header reference - -### `X-BC-IP-Rate-Limit-Requests-Quota` +## Response headers -Current configured per-IP in-flight complexity threshold used by limiter evaluation for this request. +When the protection layer is applied, responses may include headers that indicate your current usage relative to the limit. These values are for monitoring and tuning only; they are not part of a public SLA and may change. -### `X-BC-IP-Rate-Limit-Requests-Quota-Left` +| Header | Description | +|--------|-------------| +| `X-BC-IP-Rate-Limit-Requests-Quota` | Current limit (complexity) used for evaluation. | +| `X-BC-IP-Rate-Limit-Requests-Quota-Left` | Remaining complexity before the limit. | -Remaining per-IP in-flight complexity before crossing threshold, based on current in-flight counters. +Lower remaining capacity means you are closer to the limit. Do not hardcode logic around specific numeric values—use the headers as a signal and react with backoff or reduced concurrency when you see `429` or declining headroom. --- -## All response header combinations and examples - -> Note: examples show realistic values; exact numbers vary per environment and traffic state. - -### Case A: Per-IP limiter evaluated, request allowed - -- Typical status: `200` (or any non-429 business status) -- Headers: - -```http -X-BC-IP-Rate-Limit-Requests-Quota: 50000 -X-BC-IP-Rate-Limit-Requests-Quota-Left: 35000 -``` - -Meaning: request passed and the limiter reports current quota and remaining headroom. - -### Case B: Per-IP limiter evaluated, request rejected - -- Status: `429 Too Many Requests` -- Headers: - -```http -X-BC-IP-Rate-Limit-Requests-Quota: 50000 -X-BC-IP-Rate-Limit-Requests-Quota-Left: 750 -``` - -Meaning: request was rejected because this IP exceeded per-IP in-flight complexity threshold. - ---- - -## Quota value is not a public contract - -The `X-BC-IP-Rate-Limit-Requests-Quota` header reflects the **current configured per-IP in-flight complexity threshold**. - -- It can be changed without notice. -- It should **not** be treated as a fixed, publicly advertised SLA number. - -In other words: do not hardcode logic around a specific value. Always read and react to headers dynamically. - -## Operational guidance +## When you get 429 -- Treat `429` as retryable with backoff/jitter. -- Reduce burst concurrency per IP. -- Cache read-heavy queries. -- Avoid redundant page-load calls. -- Inspect `X-BC-IP-Rate-Limit-Requests-Quota` for the configured quota value (the maximum allowed request weight for the current window). -- Inspect `X-BC-IP-Rate-Limit-Requests-Quota-Left` to understand how much quota remains and whether you are approaching the threshold. -- Calculate the current request’s query complexity by subtracting `X-BC-IP-Rate-Limit-Requests-Quota-Left` from `X-BC-IP-Rate-Limit-Requests-Quota`. - The difference represents how much of the quota has been consumed in the current window, - which reflects the effective complexity of the executed query. +- Treat `429` as retryable with backoff and jitter. +- Reduce how many concurrent GraphQL requests you send. +- Cache read-heavy queries and avoid redundant calls. +- If you need more throughput, design queries to request only the data you need so each request is lighter. From 6c4eee2b395a2d44fb749d9ef0334bb4c5f09f78 Mon Sep 17 00:00:00 2001 From: "oleksiy.rykhalskyy" Date: Wed, 25 Feb 2026 14:54:00 +0200 Subject: [PATCH 10/12] frontmatter --- docs/storefront/graphql/limiter.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/storefront/graphql/limiter.mdx b/docs/storefront/graphql/limiter.mdx index d767583c0..9fd3b1254 100644 --- a/docs/storefront/graphql/limiter.mdx +++ b/docs/storefront/graphql/limiter.mdx @@ -1,3 +1,8 @@ +--- +title: Storefront GraphQL platform protection +keywords: graphql, rate limiting, 429, protection, throttling +--- + # Storefront GraphQL platform protection Storefront GraphQL is protected from over-consumption so that traffic remains fair and stable for all users. This page describes how that protection works from a client perspective. From d81895addf1a137ecf7d314488f7c0b26bcf339a Mon Sep 17 00:00:00 2001 From: "oleksiy.rykhalskyy" Date: Wed, 25 Feb 2026 14:57:09 +0200 Subject: [PATCH 11/12] more links --- docs/storefront/graphql/index.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/storefront/graphql/index.mdx b/docs/storefront/graphql/index.mdx index 844fd93ab..cbc4bd5b6 100644 --- a/docs/storefront/graphql/index.mdx +++ b/docs/storefront/graphql/index.mdx @@ -455,6 +455,9 @@ When you send a valid request to the GraphQL Storefront API, the API returns the If a query's complexity score exceeds the complexity limit, an error response similar to the following appears: +## Concurrent complexity limits + +For more information about how the platform protects against over-consumption and how to handle 429 responses, see [Storefront GraphQL platform protection](/docs/storefront/graphql/limiter). ```json filename="Example response with complexity error" showLineNumbers copy { @@ -705,6 +708,9 @@ For a list of GraphQL error messages, see [API Status Codes](/docs/start/about/s ## Related resources +### Documentation +* [Storefront GraphQL platform protection](/docs/storefront/graphql/limiter) + ### Tools * [GraphQL Cheat Sheet](https://devhints.io/graphql) From 9d6f8e721a90f6924e8908a196bd722f0bc13b6d Mon Sep 17 00:00:00 2001 From: "oleksiy.rykhalskyy" Date: Wed, 25 Feb 2026 15:51:18 +0200 Subject: [PATCH 12/12] better --- docs/storefront/graphql/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/storefront/graphql/index.mdx b/docs/storefront/graphql/index.mdx index cbc4bd5b6..160989182 100644 --- a/docs/storefront/graphql/index.mdx +++ b/docs/storefront/graphql/index.mdx @@ -455,7 +455,7 @@ When you send a valid request to the GraphQL Storefront API, the API returns the If a query's complexity score exceeds the complexity limit, an error response similar to the following appears: -## Concurrent complexity limits +## Platform protection For more information about how the platform protects against over-consumption and how to handle 429 responses, see [Storefront GraphQL platform protection](/docs/storefront/graphql/limiter).