Rate Limit APIs

Rate limit

v1.86. Every handler supports rate-limiting via the ratelimit config. For example, this allows 50 hits per user per day:

url:
  api:
    pattern: /api
    handler: FormHandler # or any handler
    kwargs:
      # ...
      ratelimit:
        keys: [daily, user]
        limit: 50

Rate limit example

Rate limit keys

keys define how to rate-limit. It’s an array of strings, or a comma-separated list of strings:

        # Set a weekly limit by user
        keys: [weekly, user]
        # Another way to set weekly limit by user
        keys: weekly, user
        # Set a daily limit globally
        keys: [daily]

keys can be:

Ratelimit key functions

keys can also be defined with functions. For example:

keys:
  # Restrict by user's email domain name
  - function: handler.current_user.email.split('@')[-1]
  # Refresh every 10 days
  - function: pd.Timestamp.utcnow().ceil(freq='10D').isoformat()
    expiry: int((pd.Timestamp.utcnow().ceil(freq='10D') - pd.Timestamp.utcnow()).total_seconds())
  # Refresh every 30 minutes
  - function: pd.Timestamp.utcnow().ceil(freq='30T').isoformat()
    expiry: int((pd.Timestamp.utcnow().ceil(freq='30T') - pd.Timestamp.utcnow()).total_seconds())

v1.92. Use key: expression to define a time-based keys like hourly, daily, weekly, monthly or yearly:

        keys:
          # Set different frequencies for different users
          - key: 'daily' if handler.current_user['role'] == 'admin' else 'monthly'

Rate limit limit

limit is the maximum number of successful requests to the page. This can be a number, or a function that returns a number. For example:

# Set a constant limit of 50
        limit: 50
# Limit to 50 for logged-in users, 10 for others
        limit: {function: 50 if handler.current_user else 10}

Rate limit headers

On each request, Gramex computes the keys and limit, looks up usage for that key, and sets these HTTP headers:

If the usage exceeds the limit, Gramex raises a HTTP 429 Too Many Requests response. This can be formatted via a custom error page.

If the response is successful, the usage increments by 1. If the response is a HTTP 5xx or HTTP 4xx, usage stays the same.

Rate limit pools

If a single API has multiple URLs (e.g. /api1, /api2, etc), add the same ratelimit.pool: to all. This combines their usage. For example:

url:
  page1:
    pattern: /api1
    handler: FormHandler # or any handler
    kwargs:
      # ...
      ratelimit: &API_POOL
        pool: my-api-pool
        keys: [daily, user]
        limit: 50
  page2:
    pattern: /api2
    handler: FormHandler # or any handler
    kwargs:
      # ...
      ratelimit:
        <<: *API_POOL # Copy config from earlier

Calling /api1 and api2 increase the SAME usage counter by 1.

Rate limit reset

To reset the API usage for any key, call handler.ratelimit_reset(pool, keys) from any FunctionHandler. For example, this sets the usage of x@example.com on 2022-01-01 to zero.

handler.ratelimit_reset('my-api-pool', ['2022-01-01', 'x@example.com'])

Rate limit store

Rate limit usage data is stored in a cache. It’s location is defined in app.ratelimit in Gramex’s own gramex.yaml:

app:
  ratelimit:
    # Save in a JSON store
    type: json
    path: $GRAMEXDATA/ratelimit.json
    # Flush every 30 seconds. Clear expired sessions every hour
    flush: 30
    purge: 3600

This configuration works exactly like sessions. To use a Redis store, use:

app:
  ratelimit:
    type: redis
    path: localhost:6379:1 # Redis server:port:db (default: localhost:6379:0)
    # flush: 30   # Redis stores are live. No flush required
    purge: 3600

Access rate limits

v1.91. You can access rate limits for the current request via handler.get_ratelimit(). This returns a rate limit object like this:

{
  "limit": 3,     # the limit on the rate limit
  "usage": 2,     # the usage so far  (BEFORE current request, e.g. 0, 1, 2, 3, ...)
  "remaining": 0, # remaining requests (AFTER current request, e.g. 2, 1, 0, 0, ...)
  "expiry": 103,  # seconds to expiry for this rate limit
}

If there are multiple rate limits, it picks the one with least remaining usage.

Multiple rate limits

v1.91. ratelimit can be an array of rate limit configurations. For example, to set 2 limit:

ratelimit:
  - pool: daily-user-pool
    keys: [daily, user]
    limit: 30
  - pool: daily-pool
    keys: [daily]
    limit: 100
  1. When Alice visits, her daily-user-pool becomes 9, and the daily-pool becomes 99
  2. When Bob visits, his daily-user-pool becomes 9, and the daily-pool becomes 98
  3. When Alice visits again, her daily-user-pool becomes 8, and the daily-pool becomes 97

The Rate limit headers are computed from the smallest remaining pool.

You can reset rate limits for each pool independently.