api · reference

Rate limits

Request quotas per API key, the headers that report remaining budget, and what happens at the limit.

Algorithm: sliding-log per API key (Upstash Redis) Window: 60 seconds Last updated: 2026-05-25

Per-plan defaults

Each API key has two independent buckets:

  • Read — GET / HEAD requests.
  • Write — POST / PUT / PATCH / DELETE.
PlanRead / minWrite / min
free305
starter6015
professional12030
business30060
enterprise600120

Writes are intentionally tighter — every write may trigger a saga, a domain event, and an outbound webhook fan-out.

Headers

Every successful response carries:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117
X-RateLimit-Reset: 42

X-RateLimit-Reset is seconds until the window resets, not a Unix timestamp.

On block (429 Too Many Requests) we additionally set Retry-After and return RFC 7807 problem details:

HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 27
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 27

{
  "type": "https://docs.vendu.app/errors/rate-limit-exceeded",
  "title": "Rate limit exceeded",
  "status": 429,
  "retry_after_sec": 27
}

Bulk requests

Bulk endpoints (POST /api/v1/{resource}/bulk) currently consume one write-bucket slot regardless of items.length (the maxBatchSize: 50 ceiling bounds blast radius). Charging N slots is a future change — when it ships, the X-RateLimit-Remaining returned with a bulk response will already reflect the multi-slot cost.

Fallback when Redis is unavailable

If UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN are not configured (local dev / unit tests) the limiter admits every request and reports remaining = limit. Production deploys must set both vars in vercel env.

Webhook-only buckets

Inbound platform webhooks (Telegram, Viber, MonoPay, …) have a separate, IP-based limiter at src/lib/rate-limit/webhooks.ts (60 req/min webhook, 30 req/min payment) — independent from the per-API-key buckets above.

Last updated 2026-07-29