← Blog

How to Build a Claude API Usage Quota System

2026-10-31 · 5 min read · SubToAPI Team

How to Build a Claude API Usage Quota System

If you're exposing Claude to users, teams, or customers, you need a way to stop any single one of them from burning through your entire budget. A usage quota system tracks how much each account has consumed, compares it against a limit, and blocks or throttles requests once that limit is hit. This article walks through the moving parts you need and a working implementation pattern.

The short version: pick a unit of measurement (tokens, requests, or cost), track consumption per account in a fast, atomic store like Redis, check the counter before every call, and increment it with the real usage numbers Claude returns after every response. Everything else — reset windows, overage handling, alerts — builds on top of that core loop.

Pick what you're actually metering

Before writing any code, decide what "usage" means for your product:

Most quota systems for internal tools or B2B SaaS use tokens, because the Claude API response already tells you exactly how many were consumed — no estimation needed.

{
  "id": "msg_01...",
  "usage": {
    "input_tokens": 512,
    "output_tokens": 128
  }
}

The core loop

A quota system needs three operations, run on every API call:

  1. Check — is this account under its limit right now?
  2. Call — if yes, make the request to Claude.
  3. Record — increment the account's counter with the real tokens used.

The tricky part is making check-and-record atomic enough that concurrent requests from the same account don't slip through before the counter updates. Redis with INCR and a TTL handles this cleanly for most use cases.

import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);

const DAILY_TOKEN_LIMIT = 500_000;

async function checkQuota(accountId) {
  const key = `quota:${accountId}:${todayKey()}`;
  const used = parseInt((await redis.get(key)) || "0", 10);
  return used < DAILY_TOKEN_LIMIT;
}

async function recordUsage(accountId, inputTokens, outputTokens) {
  const key = `quota:${accountId}:${todayKey()}`;
  const total = inputTokens + outputTokens;
  const newTotal = await redis.incrby(key, total);
  if (newTotal === total) {
    // first write of the day — set expiry
    await redis.expire(key, 60 * 60 * 24);
  }
  return newTotal;
}

function todayKey() {
  return new Date().toISOString().slice(0, 10);
}

Wrap this around your Claude call:

async function callClaude(accountId, messages) {
  const allowed = await checkQuota(accountId);
  if (!allowed) {
    throw new Error("quota_exceeded");
  }

  const response = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "x-api-key": process.env.ANTHROPIC_API_KEY,
      "anthropic-version": "2023-06-01",
      "content-type": "application/json",
    },
    body: JSON.stringify({
      model: "claude-sonnet-4-5",
      max_tokens: 1024,
      messages,
    }),
  });

  const data = await response.json();
  await recordUsage(
    accountId,
    data.usage.input_tokens,
    data.usage.output_tokens
  );
  return data;
}

This is the skeleton. Everything from here is refinement.

Handling fixed windows vs sliding windows

The example above uses a fixed daily window — the counter resets at midnight UTC. This is simple but has a known flaw: a user can burn their entire quota in the last minute before reset and the first minute after, effectively doubling their limit in a short window.

If that matters for your use case, use a sliding window instead — store timestamped usage events and sum only the ones within the last 24 hours, or use a rolling bucket algorithm. It costs more in storage and compute, so only reach for it if burst abuse is a real risk.

Overage behavior: block, throttle, or bill

Once an account hits its limit, you have three options:

Whatever you choose, always return machine-readable context so client apps can react:

{
  "error": "quota_exceeded",
  "limit": 500000,
  "used": 503120,
  "resets_at": "2024-06-02T00:00:00Z"
}

Per-key vs per-account quotas

If you issue separate API keys per environment, project, or team member, you'll likely want quotas enforced at the key level, with roll-up reporting at the account level. This mirrors how most API platforms structure limits — a team has an overall budget, and individual keys draw down against it with their own soft caps to catch runaway scripts before they eat the whole team's allowance.

Skipping the plumbing

Building this correctly — atomic counters, reset windows, per-key breakdowns, usage dashboards for your own users — is a real engineering project, not an afternoon script. If you're already routing Claude traffic through SubToAPI, you get per-key usage metadata on every response out of the box, which you can feed directly into the quota logic above instead of parsing and reconciling raw Anthropic responses yourself. Check the docs and messages API reference for the exact usage fields returned, or start with the quickstart if you're setting up keys for the first time. Plans start at Solo for solo projects and scale up to Team and Scale tiers with per-seat keys — see pricing for details, or sign up to try it with a free trial.

Testing your quota system

Before shipping, verify these cases explicitly:

Questions

Should I track tokens or requests for quotas? Tokens, in almost all cases. Request counts ignore the fact that a single call can vary from a few dozen to tens of thousands of tokens, which makes cost control inaccurate.

What happens if a streamed response is cut off mid-generation? You won't get a final usage object if the connection drops before the stream completes. Record partial usage based on tokens received so far, or estimate conservatively, rather than skipping the increment entirely.

Do I need Redis specifically for this? No — any store with atomic increment and TTL support works (Postgres with SELECT ... FOR UPDATE, DynamoDB with conditional writes, etc.). Redis is popular because INCR and EXPIRE solve the atomicity and reset-window problems with almost no code.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →