Skip to content

Rate Limiting

PrimeStamp uses configurable per-client rate limiting to protect the API. Two algorithms are available: token bucket (default, burst-friendly) and sliding window (smooth limiting).

Configuration

Rate limiting is added as FastAPI middleware:

from primestamp.api.rate_limit import add_rate_limiting, RateLimitAlgorithm

app = FastAPI()

# Default: token bucket, anonymous tier
add_rate_limiting(app)

# Or customize:
add_rate_limiting(
    app,
    algorithm=RateLimitAlgorithm.SLIDING_WINDOW,
    enabled=True,
    default_tier="anonymous",
)

Full configuration via RateLimitConfig:

from primestamp.api.rate_limit import RateLimitConfig, RateLimitMiddleware

config = RateLimitConfig(
    algorithm=RateLimitAlgorithm.TOKEN_BUCKET,
    default_tier="anonymous",
    enabled=True,
    cleanup_interval_seconds=300,
    max_tracked_clients=10000,
    include_headers=True,
    exempt_paths={"/health", "/docs", "/redoc", "/openapi.json"},
)

app.add_middleware(RateLimitMiddleware, config=config)

Tiers

Each client is assigned a tier based on authentication. Limits apply per minute and per hour.

Tier Requests/min Requests/hour Burst Size Identification
anonymous 20 200 5 Client IP (no API key)
authenticated 60 1,000 15 Bearer ps_live_... or X-API-Key: ps_...
premium 200 5,000 50 Premium API key
internal 1,000 50,000 200 Internal services / test clients

Client Identification Priority

  1. Authorization: Bearer ps_live_... or Bearer ps_test_... header -- authenticated tier
  2. X-API-Key: ps_... header -- authenticated tier
  3. X-Forwarded-For or X-Real-IP header -- IP-based, anonymous tier
  4. Direct client IP -- anonymous tier

Test clients (IP testclient) are automatically assigned the internal tier.


Endpoint Weights

Not all endpoints cost the same. Expensive operations consume more of the rate limit budget.

Endpoint Prefix Cost Multiplier Rationale
/stamps 2.0x Stamp creation involves hashing and anchoring
/fingerprints 1.5x Fingerprinting is compute-intensive
/verify 1.0x Standard cost
/identities 1.0x Standard cost
/health 0.1x Health checks are lightweight
/presets 0.1x Static configuration lookups

A request to POST /stamps from an anonymous client (burst size 5) costs 2.0 tokens, so only 2 rapid stamp creations are possible before throttling.


Response Headers

Every response includes rate limit headers (unless disabled):

Header Description
X-RateLimit-Limit Maximum requests per minute for this tier
X-RateLimit-Remaining Remaining requests in the current window
X-RateLimit-Reset Seconds until the rate limit resets
X-RateLimit-Tier Client's assigned tier name

Example response headers:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 48
X-RateLimit-Reset: 60
X-RateLimit-Tier: authenticated

429 Response

When the rate limit is exceeded, the API returns HTTP 429:

{
  "error": "rate_limit_exceeded",
  "message": "Too many requests. Please slow down.",
  "retry_after": 3.2,
  "tier": "anonymous",
  "limit": 20
}

Additional headers on 429 responses:

Header Description
Retry-After Seconds to wait before retrying (integer, rounded up)
X-RateLimit-Limit Tier limit
X-RateLimit-Remaining 0
X-RateLimit-Reset Same as Retry-After

Algorithms

Token Bucket (default)

The token bucket algorithm allows short bursts up to burst_size while enforcing a sustained rate. Tokens refill at requests_per_minute / 60 tokens per second.

  • Allows bursty traffic patterns
  • Capacity equals the tier's burst_size
  • Separate buckets for per-minute and per-hour limits

Sliding Window

The sliding window algorithm counts requests within a rolling time window. Provides smoother rate limiting without burst allowance.

  • Window sizes: 60 seconds (minute) and 3600 seconds (hour)
  • Requests outside the window are discarded
  • No burst tolerance

Per-Route Rate Limiting

As an alternative to middleware, use the dependency-based approach for per-route control:

from fastapi import Depends
from primestamp.api.rate_limit import rate_limit_dependency

@app.post("/stamps", dependencies=[Depends(rate_limit_dependency)])
async def create_stamp(...):
    ...

This raises HTTPException(429) directly if the limit is exceeded.


Exempt Paths

The following paths are exempt from rate limiting by default:

  • /health
  • /docs
  • /redoc
  • /openapi.json

Customize via RateLimitConfig.exempt_paths.


Monitoring

Get rate limiter statistics:

from primestamp.api.rate_limit import get_rate_limiter_store

store = get_rate_limiter_store()
stats = store.get_stats()
{
  "algorithm": "token_bucket",
  "tracked_clients": 142,
  "max_tracked": 10000,
  "enabled": true
}

Stale client entries (inactive for 1 hour) are cleaned up automatically every 300 seconds or when max_tracked_clients is exceeded.