FAQ: Upcoming API Gateway and Rate Limiting Changes

Prev Next

Starting December 1, 2026, Cato is gradually introducing a new API gateway that includes changes to API rate limiting. The new model includes:

  • Points-based rate limiting: Each operation consumes points based on its cost, rather than counting each call equally

  • A shared account budget: Operations draw points from the same account budget, which replenishes over time

  • Additional operation budgets: Some operations have their own budget in addition to the shared account budget

  • Retry guidance: API responses indicate how long to wait before retrying, or when to simplify or split a request that exceeds capacity

IMPORTANT: This is not a breaking change. There is no degradation to your existing integrations, and no action is required.


FAQ

Do I Need to Update My Existing Integrations?

No. This update does not introduce breaking changes, and your existing integrations continue to work.

You can use this update as an opportunity to optimize your existing API flows and processes:

  • Use cost in extensions.rateLimit to understand request costs

  • Use the Retry-After header or retryAfter response field to determine when to retry rate-limited requests

How Does the New Model Differ From the Current Model?

The current model primarily limits queries and mutations by the number of calls within a time interval. Limits apply across the API keys accessing the same account.

The new model tracks the cost of requests against available budgets.

AspectCurrent ModelNew Model
Basis for rate limitingNumber of calls within a time intervalRequest cost in points
Capacity shared across operationsLimits primarily apply separately to operations, with some shared limitsOperations consume a shared account budget
Additional limitsSpecific queries and mutations have exceptions to the default limitSome operations have their own budget in addition to the shared account budget. Requests must fit within both budgets
Retry handlingIntegrations use their existing retry logicResponses provide retry timing or indicate that the request needs to change

For example, an eventsFeed request can be rate limited by its operation-specific budget even if the shared account budget still has capacity.

What Are Points and Request Costs?

Points represent an operation’s cost for rate limiting. They are not API keys or authentication tokens.

Different operations can have different costs. The cost can also vary depending on the data you request. For example, requesting fewer fields can reduce a request’s cost.

When a request contains multiple operations, its cost includes their combined cost. Combining operations into one request does not bypass rate limiting.

How Do Budgets Replenish?

Each budget has a maximum capacity and replenishes over time. Requests consume available points.

A request can run only when every applicable budget has enough capacity to accommodate it. These can include:

  • Your account’s shared budget

  • Additional budgets for specific operations

If a budget temporarily has insufficient capacity, the request is rate limited until enough capacity becomes available.

Can One Integration Affect Other Integrations in My Account?

Yes. Requests share the account budget, including requests made with different API keys.

For example, an integration calling accountMetrics and another calling eventsFeed both consume capacity from the shared account budget. Their requests can also be subject to additional operation-specific budgets.

A request can therefore be rate limited even when that operation has made relatively few calls. Other operations can consume the shared capacity it needs.

Do the Existing Calls-Per-Minute Limits Still Apply?

The existing default of 120 calls per minute does not describe the new model. Request costs and available budgets determine whether a request can run.

Some operations have additional budgets to protect the underlying services. These budgets apply alongside the shared account budget.

Do not interpret the previous query and mutation exception values as guaranteed independent rates under the new model. Requests for different operations now compete for shared capacity.

What Response Do I Receive When a Request Is Rate Limited?

Rate-limited requests return HTTP status code 429 and a reason indicating how to proceed.

ReasonMeaningAction
BUDGET_EXHAUSTEDA request fits within the applicable budgets’ maximum capacities, but insufficient capacity is currently availableWait for the indicated interval before retrying
REQUEST_EXCEEDS_CAPACITYA request exceeds the maximum capacity of an applicable budgetSimplify or split the request

These responses distinguish temporary throttling from a request that cannot fit even after a budget fully replenishes.

How Do I Use Retry-After and retryAfter?

For BUDGET_EXHAUSTED, the response includes retry timing in seconds:

  • Retry-After: An HTTP response header

  • retryAfter: An extension field in the API response

Wait for the indicated interval before retrying the same request. The timing relates to that request and does not reserve capacity. Other requests in your account can consume capacity while you wait.

Basic Retry Handling With curl

When retries are enabled, curl recognizes the standard Retry-After header and uses it to determine when to retry.

curl --retry 5 --retry-max-time 60 \
  -H 'content-type: application/json' -H "x-api-key: $CATO_API_KEY" \
  -d '{"query":"{ accountSnapshot(accountID: 123) { id } }"}' \
  https://api.example/graphql

However, curl does not distinguish REQUEST_EXCEEDS_CAPACITY from temporary budget exhaustion. Retrying a request that exceeds maximum capacity does not resolve the issue.

Handle Complex Queries With Python

For more complex queries, inspect the rejection reason before retrying:

  • For BUDGET_EXHAUSTED, wait for the interval specified in Retry-After, then retry

  • For REQUEST_EXCEEDS_CAPACITY, simplify or split the request instead of retrying it unchanged

The following example stops when a request exceeds capacity and limits the number of attempts for temporary rate limiting.

import os
import time
import requests

API_URL = "https://api.catonetworks.com/api/v1/graphql2"
API_KEY = os.environ["CATO_API_KEY"]
MAX_ATTEMPTS = 5


def run_query(query, variables=None):
    for attempt_number in range(1, MAX_ATTEMPTS + 1):
        response = requests.post(
            API_URL,
            json={"query": query, "variables": variables},
            headers={"x-api-key": API_KEY},
        )

        if response.status_code != 429:
            return response.json()

        error_details = response.json()["errors"][0]["extensions"]
        if error_details["reason"] == "REQUEST_EXCEEDS_CAPACITY":
            raise RuntimeError(
                "This request costs more than your rate limit allows. "
                "Split it into smaller requests."
            )

        wait_seconds = int(response.headers["Retry-After"])
        print(f"Rate limited, retrying in {wait_seconds}s "
              f"(attempt {attempt_number}/{MAX_ATTEMPTS})")
        time.sleep(wait_seconds)

    raise RuntimeError(f"Still rate limited after {MAX_ATTEMPTS} attempts")

Does Waiting for the Retry Interval Guarantee Success?

No. Retry guidance reflects the capacity needed for the request when the response is generated. It does not reserve capacity.

Other requests can consume points while your integration waits. If several integrations retry concurrently, some requests can succeed while others receive another rate limiting response.

If your retry receives BUDGET_EXHAUSTED again, use the retry timing from the latest response.

What is the REQUEST_EXCEEDS_CAPACITY Response?

REQUEST_EXCEEDS_CAPACITY means the request itself is too large for the API gateway to handle at once. This can happen if one queries the same field using aliases multiple times, which then creates a larger request.

Can the Gateway Execute Part of a Rate-Limited Request?

No. For rate limiting, the gateway accepts or rejects the entire request.

If a request contains several operations and cannot fit all applicable budgets, the gateway rejects it rather than executing only the operations that fit.

How Can I Get Information About a Request’s Cost?

You can request optional extensions.rateLimit response details containing cost and retryAfter.

FieldPurpose
costProvides information about the request’s point cost
retryAfterProvides retry timing information in seconds

Use cost information for the specific request you intend to send. Do not assume that every request using the same query or mutation has the same cost.

Can I Check a Request’s Cost Without Executing It?

Yes. The new gateway supports a request option, dryRun: true, that estimates a query or mutation’s cost without executing it or consuming budget.

A dry run reports cost only. It does not report current budget availability or guarantee that a subsequent request can run.

For mutations, a dry run does not perform the requested change.

In the following example, dryRun only shows requests that can be admitted, includeDetails shows the numbers, cost of the request, retry after, and cost breakdown.

POST /graphql
{
  "query": "query Snap($id: ID!) { accountSnapshot(accountID: $id) { id sites { id } users { id } } }",
  "variables": { "id": "123" },
  "operationName": "Snap",
  "extensions": {
    "rateLimit": {
      "dryRun": true,
      "includeDetails": true
    }
  }
}

Do I Need to Maintain a List of Operation Costs?

No. You do not need to calculate every operation’s cost to use the API.

Your integration can respond to rate limiting using the returned reason and retry guidance. Use optional cost information when you want to analyze or optimize specific requests.