Quota API

Deterministic per-request quota enforcement with fixed reset windows.

API Docs

Overview

Introduction

Most modern products need guardrails on usage. Examples: • A dating app caps swipes or messages per user per day • A SaaS platform limits how often a host can be powered off per hour • An AI API caps requests per tenant per day Implementing these limits correctly is harder than it looks: reset semantics, retries, idempotency, and enforcement behavior quickly become operational burdens. Quota API sits in the request path and makes a deterministic allow / block decision for each request. You define a stable resource_key, attach a quota rule that describes limits and reset behavior, then check or consume usage per subject_id. Throughout these docs, we use a single example: limiting how many apples can be discarded per subject per day. The same pattern applies to any feature you need to meter.

v1

Core Concepts

  • Resource — A stable identifier for the thing being limited (e.g., "feature-action", "sms-send", "api-requests").
  • subject_id — The entity whose usage is being tracked (e.g., user ID, tenant ID, API key, device ID).
  • Quota rule — Policy attached to a resource that defines how much usage is allowed and when it resets.
    • quota_policy — "limited" enforces a hard cap; "unlimited" tracks usage without applying a cap.
    • enforcement_mode — "enforced" blocks requests that exceed the quota; "non_enforced" records usage but always allows.
    • reset_strategy — Controls when usage resets using a unit (hour/day/week/month/year/never) and an interval (>0, ignored for "never").
  • idempotency — request_id ensures retries do not double-consume usage.
v1

Example: Implementing a daily limit

Goal: set a per-subject daily limit for an action. The workflow is always the same: 1. Create a resource_key representing the action 2. Attach a limited quota rule with a daily reset 3. Check or consume usage per subject This exact pattern generalizes to per-hour, per-month, or lifetime limits.

v1
HTTP Request
1) POST /v1/resources
      → create "sample-resource"

2) POST /v1/quota-rules
      → quota_policy=limited
      → quota_limit=100
      → reset_strategy={ unit: "day", interval: 1 }
      → enforcement_mode=enforced

3) POST /v1/quota/check
      → preflight a discard

4) POST /v1/quota/consume
      → record a discard (amount=1) with request_id for idempotency

Use reset_strategy { unit: "day", interval: 1 }; set enforcement_mode=enforced to block over-limit discards.

DevKit & Examples

The DevKit provides concrete, runnable examples of how to integrate the Quota API. It includes Postman collections, an OpenAPI specification, curl requests, and code snippets you can run locally. Use it to explore endpoints, validate enforcement behavior, and share reference integrations with your team.

v1

GitHub: quota-devkit (includes Postman, OpenAPI, examples).

Environment & Base URL

Base URL

Use this url for all API requests: https://quota-api.thrysha.io

v1

Authentication

Authentication

API keys are required for every endpoint. Keep them server-side only, rotate them regularly, and scope them per environment. Requests with missing or invalid keys are rejected before any work is done.

v1
HTTP Request
Authorization: Bearer <API_KEY>
Content-Type: application/json

Resources API

Create Resource

Create the resource your quota will protect. Use a stable resource_key for the action or feature you need to meter.

v1
HTTP Request
POST /v1/resources

{
  "resource_key": "apples-discard",
  "description": "Used by service A"
}
Response
{
  "id": "res_abcd1234",
  "account_id": "acct_123",
  "resource_key": "apples-discard",
  "description": "Used by service A",
  "created_at": "2025-01-10T12:34:56Z"
}
Business Rules
  • resource_key is required, must match ^[a-z0-9][a-z0-9_-]{1,62}$, and is unique per account (case-insensitive).
  • Resources belong to the caller's account; account_id cannot be overridden.
  • Each account can create up to 100,000 resources (ERR_RESOURCE_LIMIT_REACHED when exceeded).
  • Use stable identifiers; rename is preferred over delete/recreate.
Errors
  • 401 UnauthorizedMissing or invalid API key.
  • 400 Bad RequestInvalid payload (e.g., missing "resource_key", empty after trim).
  • 409 ConflictA resource with the same key already exists for this account.
  • ERR_RESOURCE_LIMIT_REACHEDAccount has reached the maximum number of resources (100,000).
Sample code
const res = await fetch("https://quota-api.thrysha.io/v1/resources", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <API_KEY>",
  },
  body: JSON.stringify({ resource_key: "apples-discard", description: "Used by service A" }),
});
const data = await res.json();

List Resources

Enumerate resources to audit what is being metered and surface keys for automation.

v1
HTTP Request
GET /v1/resources?page=1&page_size=50
Response
{
  "items": [
    {
      "id": "res_abcd1234",
      "account_id": "acct_123",
      "resource_key": "apples-discard",
      "description": "Used by service A",
      "created_at": "..."
    }
  ],
  "page": 1,
  "page_size": 50,
  "total": 1
}
Business Rules
  • Results are scoped to the authenticated account.
  • page defaults to 1 (must be ≥ 1); page_size defaults to 50 and is capped at 200.
Errors
  • 401 UnauthorizedMissing or invalid API key.
  • 400 Bad RequestInvalid page or page_size.
Sample code
const res = await fetch("https://quota-api.thrysha.io/v1/resources?page=1&page_size=50", {
  headers: {
    "Authorization": "Bearer <API_KEY>",
  },
});
const data = await res.json();

Delete Resource

Use for cleanup when a resource is retired. Deleting removes it from future listings; ensure you have no active rules or dependencies before deleting. Matching is case-insensitive.

v1
HTTP Request
DELETE /v1/resources/{resourceKey}
Response
{ "status": "deleted" }
Business Rules
  • Resource must belong to the authenticated account.
Errors
  • 401 UnauthorizedMissing or invalid API key.
  • 404 Not FoundNo resource with this key for the account.
Sample code
await fetch("https://quota-api.thrysha.io/v1/resources/apples-discard", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer <API_KEY>",
  },
});

Quota Rules API

Create Quota Rule

Attach a quota rule to a resource to define how much usage is allowed, when it resets, and whether over-limit usage is blocked.

Common configurations
Hard limit with blocking (every 5 days)
quota_policy=limited
enforcement_mode=enforced
quota_limit=200
reset_strategy={ unit: "day", interval: 5 }
Measure first, block later (rollout / brownfield)
quota_policy=limited
enforcement_mode=non_enforced
quota_limit=500
reset_strategy={ unit: "week", interval: 1 }
Monitoring only (no blocking)
quota_policy=unlimited
enforcement_mode=non_enforced
reset_strategy={ unit: "month", interval: 1 }

Send quota_limit if you want remaining/limit reported for dashboards.
Tight hourly guardrail
quota_policy=limited
enforcement_mode=enforced
quota_limit=100
reset_strategy={ unit: "hour", interval: 1 }
Reset strategy

reset_strategy controls when usage resets:

unit: hour | day | week | month | year | never
interval: positive integer (> 0), ignored when unit = "never"
v1
HTTP Request
POST /v1/quota-rules

{
  "resource_key": "apples-discard",
  "quota_limit": 1000,
  "quota_policy": "limited",              // optional, defaults to "limited"
  "reset_strategy": {
    "unit": "hour",                       // hour | day | week | month | year | never
    "interval": 1                         // > 0; ignored for "never"
  },
  "enforcement_mode": "enforced"          // or "non_enforced"
}
Response
{
  "id": "qr_123",
  "resource_key": "apples-discard",
  "quota_policy": "limited",
  "quota_limit": 1000,
  "reset_strategy": { "unit": "hour", "interval": 1 },
  "enforcement_mode": "enforced",
  "created_at": "..."
}
Business Rules
  • resource_key must belong to the authenticated account; only one active quota rule may exist per resource.
  • Use quota_policy=limited when you need a hard ceiling (quota_limit required).
  • limited + enforcement_mode=enforced blocks over-limit requests (typical production).
  • limited + enforcement_mode=non_enforced measures impact without blocking (useful during rollout).
  • quota_policy=unlimited is monitoring-only; it never blocks but still tracks usage (send quota_limit for reporting).
  • reset_strategy.unit supports hour/day/week/month/year/never; interval must be > 0 (ignored for never).
  • Windows align to UTC boundaries for the chosen unit (e.g., hour at :00 UTC, day at 00:00 UTC).
  • Creating a rule for a resource that already has one returns ERR_CREATE_QUOTA_RULE_FAILED.
Errors
  • 401 UnauthorizedMissing or invalid API key.
  • 400 Bad RequestInvalid combination of fields or quota_limit <= 0.
  • 404 Not Foundresource_key does not exist or is not owned by this account.
  • ERR_CREATE_QUOTA_RULE_FAILEDA quota rule already exists for this resource.
Sample code
await fetch("https://quota-api.thrysha.io/v1/quota-rules", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <API_KEY>",
  },
  body: JSON.stringify({
    resource_key: "apples-discard",
    quota_policy: "limited",
    quota_limit: 1000,
    reset_strategy: { unit: "hour", interval: 1 },
    enforcement_mode: "enforced",
  }),
});

List Quota Rules

Inspect current limits and reset strategies for a resource. Useful for dashboards, audits, and ensuring clients see active enforcement settings.

v1
HTTP Request
GET /v1/quota-rules?resource_key=apples-discard&page=1&page_size=50
Response
{
  "items": [
    {
      "id": "qr_123",
      "resource_key": "apples-discard",
      "quota_limit": 1000,
      "quota_policy": "limited",
      "reset_strategy": "fixed_window",
      "reset_interval_seconds": 3600,
      "enforcement_mode": "enforced",
      "created_at": "..."
    }
  ],
  "page": 1,
  "page_size": 50,
  "total": 1
}
Business Rules
  • resource_key is required.
  • page defaults to 1 (must be ≥ 1); page_size defaults to 50 and is capped at 200.
Errors
  • 401 UnauthorizedMissing or invalid API key.
  • 404 Not Foundresource_key does not exist or is not owned by this account.
  • 400 Bad RequestInvalid page or page_size.
Sample code
const res = await fetch("https://quota-api.thrysha.io/v1/quota-rules?resource_key=apples-discard&page=1&page_size=50", {
  headers: {
    "Authorization": "Bearer <API_KEY>",
  },
});
const data = await res.json();

Delete Quota Rule

Stop enforcing or observing usage for a resource. Ensure you create a replacement rule if the resource still needs coverage.

v1
HTTP Request
DELETE /v1/quota-rules/{ruleID}
Response
{ "status": "deleted" }
Business Rules
  • Only one rule per resource; deleting removes enforcement entirely.
Errors
  • 401 UnauthorizedMissing or invalid API key.
  • 404 Not FoundNo quota rule with this ID for the account.
Sample code
await fetch("https://quota-api.thrysha.io/v1/quota-rules/qr_123", {
  method: "DELETE",
  headers: {
    "Authorization": "Bearer <API_KEY>",
  },
});

Quota Evaluation API

Check Quota

Preflight usage by calling check on your resource_key with a subject_id; amount=0 performs a read-only peek per subject. Use check when you want a read-only decision; use consume when you want the decision to count toward usage.

v1
HTTP Request
POST /v1/quota/check

{
  "resource_key": "apples-discard",
  "subject_id": "sub_1234",
  "amount": 0
}
Response
{
  "allowed": true,
  "remaining": 975,
  "limit": 1000
}
Business Rules
  • subject_id scopes usage per subject (user/tenant); include it for every check.
  • amount must be >= 0; use amount=0 for a read-only peek.
  • Resource must already have a quota rule, otherwise ERR_NO_QUOTA_RULE.
  • For non_enforced rules, response is always allowed=true but still reports remaining/limit.
Errors
  • 401 UnauthorizedMissing or invalid API key.
  • 404 Not Foundresource_key does not exist or is not owned by this account.
  • ERR_NO_QUOTA_RULENo quota rule configured for the resource.
  • 400 Bad RequestInvalid payload (e.g., negative amount).
Sample code
const res = await fetch("https://quota-api.thrysha.io/v1/quota/check", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <API_KEY>",
  },
  body: JSON.stringify({ resource_key: "apples-discard", subject_id: "sub_1234", amount: 0 }),
});
const data = await res.json();

Consume Quota

Use this to record usage and enforce limits. Provide a stable request_id to avoid double-charging on retries. Enforced rules block when limits are exceeded; non_enforced rules only log usage.

v1
HTTP Request
POST /v1/quota/consume

{
  "resource_key": "apples-discard",
  "subject_id": "sub_1234",
  "amount": 25,
  "request_id": "unique-idempotency-key"
}
Response
{
  "allowed": true,
  "remaining": 950
}
Business Rules
  • subject_id scopes usage per subject (user/tenant); include it for every consume.
  • amount must be > 0 (ERR_INVALID_AMOUNT otherwise).
  • Provide a stable request_id per idempotent action; replays return the prior decision.
  • non_enforced rules never block; enforced rules return allowed=false when limits would be exceeded.
  • Fails with ERR_NO_QUOTA_RULE if the resource has no rule.
Errors
  • 401 UnauthorizedMissing or invalid API key.
  • 404 Not Foundresource_key does not exist or is not owned by this account.
  • ERR_NO_QUOTA_RULENo rule is configured for this resource.
  • ERR_INVALID_AMOUNTamount must be greater than 0.
  • ERR_QUOTA_CONSUME_FAILEDQuota cannot be consumed (generic failure).
  • 409 Conflictrequest_id already used with different parameters.
Sample code
const res = await fetch("https://quota-api.thrysha.io/v1/quota/consume", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <API_KEY>",
  },
  body: JSON.stringify({
    resource_key: "apples-discard",
    subject_id: "sub_1234",
    amount: 25,
    request_id: "unique-idempotency-key",
  }),
});
const data = await res.json();
Usage Durability Characteristics

Usage increments are stored in memory and flushed periodically. In rare failure scenarios (e.g., Redis process crash), increments from the last few minutes may not be persisted.

This affects reporting accuracy for the most recent window but does not affect quota enforcement, which always uses the live counter state at the moment of the request.