NullSpend Docs

rate-limits

Rate Limits

The NullSpend proxy enforces rate limits to protect against abuse. Two layers run concurrently: per-IP and per-key.


Two Layers

LayerLimitIdentifierScope
Per-IP120/mincf-connecting-ipAll requests
Per-key600/minx-nullspend-keyAuthenticated requests

Both layers use Cloudflare's native rate limiting binding, which runs on the same machine as the Worker with ~0ms overhead. Limits are configured in wrangler.jsonc.


Enforcement Order

  1. IP rate limit + auth — Run in parallel. Neither depends on the other.
  2. Key rate limit — Checked as part of the rate limiting step, only if a valid x-nullspend-key header is present.
  3. Either failure → 429 immediately. The request never reaches the upstream provider.

Rate limiting runs concurrently with authentication in the request pipeline.


Edge Cases

  • Missing or empty x-nullspend-key — Only the IP limit is checked. The key limit is skipped.
  • Key longer than 128 characters — Treated as invalid for rate limiting purposes. Only the IP limit is checked.
  • Per-colo counting — Cloudflare native rate limiting counts per data center (colo), not globally. A user hitting multiple colos gets separate counters. This is acceptable for abuse protection.

Response Headers

When a rate limit is exceeded, the 429 response includes:

HeaderValue
Retry-AfterSeconds until it's safe to retry (currently 60)

The response body follows the standard error format:

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests",
    "details": null
  }
}

Failure Mode: Fail-Open

If the rate limiting binding is unavailable, rate limiting is skipped silently. The request proceeds as if no rate limit exists.

This is intentional: rate limiting is protective, not a correctness requirement. Budget enforcement — which is independent of rate limiting — still applies and fails closed (returns 503).


Rate Limits vs Budget Enforcement

Rate LimitsBudget Enforcement
What it capsRequest count per minuteDollar spend per period
Failure modeFail-open (skip if binding unavailable)Fail-closed (503 if unavailable)
Error coderate_limitedbudget_exceeded / velocity_exceeded
ScopePer-IP + per-keyPer-user + per-key + per-tag
Pipeline positionParallel with authInside route handler

On this page