Rate Limits

Sliding-window rate limits and how to read the X-RateLimit headers and 429 responses.

The API applies sliding-window rate limits before any authentication-aware routes. Every response carries the current usage, and exceeding the limit returns 429 Too Many Requests with a Retry-After header.

Policies

CallerLimit
Authenticated actor (distributor / partner / organization)5 requests / second
Unauthenticated (per IP)1 request / second

Rate-limit keys are derived from the JWT or API key actor for authenticated requests, and from the client IP otherwise.

Excluded paths

These paths are not rate-limited so monitoring and documentation always work:

  • GET /healthz
  • GET /readiness
  • All paths under /docs/ (OpenAPI spec and HTML docs)

Response headers

Every API response includes:

HeaderMeaning
X-RateLimit-LimitMaximum requests allowed in the window.
X-RateLimit-RemainingRequests remaining in the current window.
X-RateLimit-ResetUnix timestamp (seconds) when the window resets.

On 429 Too Many Requests responses you also get:

HeaderMeaning
Retry-AfterNumber of seconds to wait before retrying.

Example: under the limit

GET https://api.benjipays.com/v2/whoami
Authorization: Bearer YOUR_ACCESS_TOKEN
HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 4
X-RateLimit-Reset: 1704067201

Example: rate limit exceeded

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

Body:

{
  "type": "https://api.benjipays.com/problems/too_many_requests",
  "title": "Too Many Requests",
  "status": 429,
  "detail": "Rate limit exceeded. Please retry after the specified time.",
  "instance": "/v2/whoami",
  "retry_after_seconds": 1
}

Parsing the headers

Node.js

const axios = require('axios');

async function makeRequest() {
  try {
    const response = await axios.get('https://api.benjipays.com/v2/whoami', {
      headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN' }
    });
    const limit = parseInt(response.headers['x-ratelimit-limit'], 10);
    const remaining = parseInt(response.headers['x-ratelimit-remaining'], 10);
    const reset = parseInt(response.headers['x-ratelimit-reset'], 10);
    console.log(`Rate limit: ${remaining}/${limit} remaining`);
    console.log(`Resets at: ${new Date(reset * 1000).toISOString()}`);
    return response.data;
  } catch (err) {
    if (err.response?.status === 429) {
      const retryAfter = parseInt(err.response.headers['retry-after'], 10);
      console.log(`Rate limit exceeded. Retry after ${retryAfter} seconds`);
    }
    throw err;
  }
}

Python

import requests, time

def make_request():
    r = requests.get(
        'https://api.benjipays.com/v2/whoami',
        headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'}
    )
    if r.status_code == 429:
        time.sleep(int(r.headers.get('Retry-After', '1')))
        return  # caller retries
    r.raise_for_status()
    return r.json()

Best practices

  • Inspect X-RateLimit-Remaining on every response and back off proactively.
  • On 429, respect Retry-After exactly — don't retry sooner.
  • Use exponential backoff if you see repeated 429s after the suggested wait.
  • Cache responses where the data tolerance allows it.
  • Don't try to evade limits with multiple IPs or accounts — limits are per actor on authenticated traffic.
  • HTTP headers are case-insensitive. Parse them as such.