Rate Limits
Ledgerline applies per-route rate limiting on all externally-facing HTTP endpoints. Limits are enforced using a fixed-window counter backed by Valkey (distributed across all Render instances).
Limits by endpoint
| Endpoint | Key | Default limit | Window |
|---|---|---|---|
POST /api/discord/interactions | IP + Discord user-id | 30 req/min | 60 s |
POST /api/easypost/webhook | Source IP | 60 req/min | 60 s |
Discord interactions
Each key is <client-ip>:<discord-user-id> when a user-id is present in the interaction body, or <client-ip> alone for unsigned or pre-authentication probes. This gives per-user throttling while still capping raw IP traffic.
EasyPost webhook
EasyPost delivers events from a fixed IP range. The limit is generous (60/min) to accommodate normal batch deliveries, but protects against replay attacks.
Response format
A rate-limited request receives a 429 Too Many Requests response:
{
"error": "Too Many Requests",
"retryAfterSeconds": 60
}
The Retry-After header is also set to the window duration in seconds.
Body size limits
In addition to rate limiting, each endpoint enforces a maximum request body size:
| Endpoint | Max body size |
|---|---|
POST /api/discord/interactions | 64 KB |
POST /api/easypost/webhook | 256 KB |
Requests exceeding the limit receive 413 Payload Too Large:
{
"error": "Payload Too Large",
"maxBytes": 65536
}
Observability
Every rate-limit rejection emits a Datadog counter metric rate_limit.exceeded with tags:
endpoint— logical endpoint name (e.g.discord_interactions,easypost_webhook)source— the key that was throttled (IP or IP:user-id)
Configuration
Rate limits are configurable via environment variables. See CONFIG.md for details.
| Variable | Description | Default |
|---|---|---|
RATE_LIMIT_DISCORD_PER_MIN | Discord interactions limit per minute | 30 |
RATE_LIMIT_EASYPOST_PER_MIN | EasyPost webhook limit per minute | 60 |
RATE_LIMIT_TENANT_PER_MIN | Tenant-level global limit per minute | 1000 |
RATE_LIMIT_DISABLED | Disable all limiters (tests only) | false |
Failure mode
If Valkey is unavailable, the rate limiter fails open — requests are allowed through and no 429 is returned. This preserves availability during cache outages. The global express.json body size limit (256 KB) still applies regardless of Valkey state.