Metrics, Billing & Quotas

#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.

#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"
    }
  }
}
json

#Policy Configuration

  • name <string> - The name of your policy instance. This is used as a reference in your routes.
  • policyType <string> - The identifier of the policy. This is used by the Zuplo UI. Value should be quota-inbound.
  • handler.export <string> - The name of the exported type. Value should be QuotaInboundPolicy.
  • handler.module <string> - The module containing the policy. Value should be $import(@zuplo/runtime).
  • handler.options <object> - The options for this policy. See Policy Options below.

#Policy Options

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

  • period (required) <string> - The period of the quota. Allowed values are hourly, daily, weekly, monthly.
  • quotaBy (required) <string> - The quota by. Allowed values are user, 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, function. Defaults to "first-api-call".
  • allowances <object> - The allowances for the quota.
  • quotaOnStatusCodes <undefined> - 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 (required) <string> - 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
      }
    }
  }
}
json

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 });
ts

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"
    }
  }
}
json

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 { GetQuotaDetailFunction } from "@zuplo/runtime";

export const getQuotaDetail: GetQuotaDetailFunction = async (
  request,
  context,
  policyName,
) => {
  return {
    key: request.user.data.organizationId,
    allowances: {
      bananas: 100,
    },
  };
};
ts

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"
    }
  }
}
json
// ./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);
};
ts

#Get Usage

You can also programmatically access the usage counts with the getUsage static function on QuotaInboundPolicy. This call must occur after the Quota-Inbound policy has executed.

const usage = QuotaInboundPolicy.getUsage(context, 'quota-policy-name');
context.log.info(usage);

// This would generate the following output:

{
  anchorDate: string;
  nextResetDate: string;
  meters: Record<string, number>;
}

// example

{
  anchorDate: "2023-08-20T03:05:05.493Z",
  nextResetDate: "2024-08-20T03:05:05.493Z",
  meters: {
    requests: 1,
    bananas: 10
  }

}
ts

Note that if the quota has not yet been updated for a particular meter, the meter will be undefined in the response.

Read more about how policies work