pr walkthrough

Per-token rate limits

Moves rate limiting from per-IP to per-token, so one office NAT can't exhaust the budget for every client behind it. Three files, one new bucket table, no behavior change for untokened requests.

Shape of the change

  • gateway/
    • limits.py+38-9
    • middleware.py+6-2
    • migrations/
      • 0042_token_buckets.sql+11

The interesting part is gateway/limits.py:41: the bucket key changes from the remote address to the token id when one is present. Everything else is plumbing that key through.

The ceilings themselves

None of these numbers move. What moves is what they are counted against: a plan's ceiling used to be spent by every client behind one address, and is now spent per token.

Ceilings per plan, and what each is counted against after this change.
Plan A minute Burst Counted against
Free 60 120 the token
Team 600 1,200 the token
Enterprise 6,000 12,000 the token, per environment
Untokened 60 60 the remote address, as before

The core diff

Why the prefixes matter

def bucket_key(self, request):
    if request.token:
        return f"tok:{request.token.id}"
Unprefixed, a token id that happens to look like an IP would share a bucket with that address. The prefixes make the two namespaces disjoint.
return f"ip:{request.remote_addr}"

Testing

Both suites run under the rate-limit marker:

# the integration half needs the migration applied first
cd gateway && alembic upgrade head
pytest tests/ -m ratelimit --maxfail=1