Policies

Quota Policy

You can use the Quota policy to limit the number of requests that are allowed to happen in a given time period (e.g., monthly). The policy can be applied the users or based on custom keys.

It supports monthly, weekly, daily and hourly quotas. By default a requests meter is incremented by 1 for every request but you can also quota by other arbitrary meters; more on this below.

Beta

This policy is in beta. You can use it today, but it may change in non-backward compatible ways before the final release.

Configuration

The configuration shows how to configure the policy in the 'policies.json' document.

{ "name": "my-quota-inbound-policy", "policyType": "quota-inbound", "handler": { "export": "QuotaInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "allowances": { "requests": 10 }, "period": "monthly", "quotaBy": "user", "quotaOnStatusCodes": "200-399" } } }

Policy Options

The options for this policy are specified below. All properties are optional unless specifically marked as required.

  • period <string> (Required) -
    The period of the quota.
    Allowed values are hourly, daily, weekly, and monthly.
  • quotaBy <string> (Required) -
    The quota by.
    Allowed values are user, and function. Defaults to "user".
  • quotaAnchorMode <string> -
    How the policy determines the anchor date for ongoing quota cycles - defaults to first-api-call which uses the first API call for this key.
    Allowed values are first-api-call, and function. Defaults to "first-api-call".
  • allowances <object> -
    The allowances for the quota.
  • quotaOnStatusCodes <string | number[]> -
    A list of successful status codes and ranges "200-299, 304" that should trigger a quota increment.
    Defaults to "200-299".
  • identifier <object> -
    The module and functions to dynamically set the anchor date and/or the key/allowances for this request.
    • module <string> (Required) -
      Specifies the module to load your custom functions, in the format $import(./modules/my-module).
      Defaults to "$import(./modules/my-module)".
    • getAnchorDateExport <string> -
      used when quotaAnchorMode is function. Specifies the export to load your custom function to get the anchor date.
      Defaults to "getAnchorDate".
    • getQuotaDetailExport <string> -
      used when quotaBy is function. Specifies the export to load your custom function to get the quota detail.
      Defaults to "getQuotaDetail".

Using the Policy

The Quota policy needs to know when to anchor the quota start date so that the Zuplo runtime can know where in the quota cycle you are. By default the runtime uses the "quotaAnchorMode": "first-api-call" which checks to see if we have an existing quota record for this user or custom quota key and, if not, sets it based on the time of the first API call for this key.

You can customize the subscription date by setting the getAnchorDateExport function, more below under Custom Anchor Date.

Quota Cycles / Periods#

The quota periods run from the anchor date and time until the next matching cycle. For monthly periods, if the anchor date is 2024-01-31 04:30Z then the quota cycle will terminate on the same day of the next month or the last day of that month if it is a shorter month, at the same time. In this case the quota cycle will reset on 2024-02-29 04:30Z (because 2024 is a leap year).

weekly cycles shift on the same day of the next week, at the same time.

daily on the next day, at the same time.

hourly on the same minute, of the next hour.

Custom Meters#

You can set custom meters in the allowances property of the options to include custom meters other than requests. For example, here we set a monthly allowance of 10 bananas.

{ "name": "my-quota-inbound-policy", "policyType": "quota-inbound", "handler": { "export": "QuotaInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "period": "monthly", "quotaBy": "user", "allowances": { "bananas": 10 } } } }

For this to work you must tell the runtime how many bananas to increment in a given request/response lifecycle. This is achieved by using the setMeters method on the QuotaInboundPolicy class:

import { QuotaInboundPolicy } from "@zuplo/runtime"; // ... QuotaInboundPolicy.setMeters(context, { bananas: 5, oranges: 3 });

This is typically invoked in a custom inbound or outbound policy or a handler.s

Dynamic Quota Allowances and Keys#

Like Dynamic Rate Limiting, Quota Keys and allowances can also be set dynamically in Zuplo. This is achieved by setting the identifier module and getQuotaDetailExport in your options:

{ "name": "my-quota-inbound-policy", "policyType": "quota-inbound", "handler": { "export": "QuotaInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "identifier": { "getQuotaDetailExport": "getQuotaDetail", "module": "$import(./modules/my-module)" }, "quotaAnchorMode": "first-api-call", "quotaBy": "user" } } }

If you wanted to key on a property from the user metadata, like organizationId you might have a getQuotaDetail implementation that looks like this.

// ./modules/my-module.ts import { GetQuotaAnchorDateFunction } from "@zuplo/runtime"; export const getQuotaDetail: GetQuotaDetailFunction = async ( request, context, policyName ) => { return { key: request.user.data.organizationId, allowances: { bananas: 100, }, }; };

Note that this method supports async calls and could be used to load quotas from another API, but we would recommend caching the results for performance reasons.

Custom Anchor Date#

Similarly, the Anchor Date can be set programmatically - for example you may load the 'subscription' start date from another database or API.

{ "name": "my-quota-inbound-policy", "policyType": "quota-inbound", "handler": { "export": "QuotaInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "identifier": { "getAnchorDateExport": "getQuotaDetail", "module": "$import(./modules/my-module)" }, "quotaAnchorMode": "first-api-call", "quotaBy": "user" } } }
// ./modules/my-module.ts import { GetQuotaAnchorDateFunction } from "@zuplo/runtime"; export const getAnchorDate: GetQuotaAnchorDateFunction = async ( request, context, policyName ) => { // simple example fetch call, needs error handling, auth etc. const response = await fetch( `https://my-subs-api/subs/${request.user.data.organizationId}` ); const data = await response.json(); return new Date(data.startDate); };

Read more about how policies work

Previous
Stripe Webhook Auth