Skip to content

Guarding Pattern: Rate Limit an API

This pattern explains how to use a Liteguard guard to enforce a per-minute rate limit on any code path, such as an API endpoint, a background job, or an expensive operation. The limit is configured in Liteguard, not hardcoded in your application.


When to use this pattern

Use this pattern when you want to:

  • Throttle calls to an endpoint to prevent abuse or overload.
  • Apply a per-user or per-account limit on an operation.
  • Tune the limit live from the Liteguard UI without deploying new code.
  • Combine a rate limit with a feature flag (guard the feature and throttle it).

How it works

A guard evaluated with isOpen returns false when the configured rate limit is exceeded for the current sliding window. The check is local. The SDK tracks call counts in memory and does not make a network call per evaluation.

When no rate limit properties are specified, the limit is a global ceiling shared across all callers. When you specify one or more property keys, each unique combination of those property values gets its own independent counter.


Step 1: Add the guard to your code

Wrap the code path you want to throttle with a guard check. Pass any properties you want to key the limit by.

js
// Node.js example
if (client.isOpen('api.image_resize', { userId: req.user.id })) {
    await processImageResize(req);
} else {
    res.status(429).json({ error: 'Rate limit exceeded. Please try again shortly.' });
}

At this point the guard is unadopted, so isOpen returns false for every call. That is expected until you configure the guard in Liteguard.


Step 2: Adopt the guard and set the rate limit

  1. Open the Guards tab in the Liteguard UI and find the guard in the Unadopted section.
  2. Click Adopt next to the guard name.
  3. On the guard detail page, scroll to the Rate limit section.
  4. Enter the maximum number of calls allowed per minute.
  5. Optionally, add one or more property keys in the Rate limit properties field. Liteguard will apply the limit independently per unique combination of those property values. For example, entering userId gives each user their own counter.
  6. Set the Default value to Open (true) so that calls within the limit return true.
  7. Save the configuration.

Step 3: Verify the behavior

After saving, calls within the limit return true and calls that exceed the limit return false. To observe this in the Liteguard UI, open the guard's detail page and watch the Catalog metrics section, which shows open versus closed signal counts in the rolling window.

To inspect the exact configuration being served to the SDK, go to Config, open your workspace, open the project, and scroll to Bundle Preview. The bundle preview shows the rate limit value and the keyed properties for each environment.


Combining a rate limit with rules

Rate limits and rules are evaluated together. Rules run first. If a rule returns a result for the evaluation, the rate limit check still applies to that result when the result is open. The order is:

  1. Rules are evaluated from top to bottom. The first matching rule determines the raw result.
  2. If no rule matches, the default value is the raw result.
  3. If the raw result is open, the rate limit counter increments. If the counter is at or over the per-minute ceiling, isOpen returns false.

This means you can combine a rule that opens the guard for paying users with a rate limit that still caps how often they can invoke it:

CallerRule matches?Rule resultRate limit exceeded?isOpen result
Free userNoDefault (closed)Not checkedfalse
Pro user, within limitYesOpenNotrue
Pro user, over limitYesOpenYesfalse

Per-user vs global limits

Global limit example (shared ceiling for all callers):

In the Liteguard UI, set the per-minute count to 100 and leave the rate limit properties field empty. All callers share a single counter. This is useful for protecting a downstream service that cannot handle more than a fixed request rate regardless of who is calling.

Per-user limit example (independent counter per user):

Set the per-minute count to 10 and add userId as a rate limit property. Each unique userId gets its own 10/minute counter. Pass the same property key from your code:

python
# Python example
if client.is_open('api.image_resize', properties={'user_id': user_id}):
    process_image_resize()
else:
    return 429, {'error': 'Rate limit exceeded. Please try again shortly.'}

Property key alignment. The property key you enter in the Liteguard UI must exactly match the key you pass in code. A userId entry in the UI matches {"userId": "..."} in code, not {"user_id": "..."}.


Adjusting limits without a deployment

Because the limit is stored in the bundle, not in your code, you can change it in the Liteguard UI at any time. The SDK picks up the new value on its next bundle refresh, which happens on a short polling interval in the background. No code change or deployment is required.


See also