Rate limits
Request limits and how to handle 429s.
VINR throttles API traffic to keep the platform fast and fair for every merchant. This page explains the per-environment limits, the headers that let you track your budget in real time, and how to back off cleanly when you hit a 429 Too Many Requests.
How limiting worksAsk
Limits are enforced per secret key using a token-bucket algorithm. Each bucket refills continuously at a fixed rate and holds a burst allowance on top, so short spikes are absorbed while sustained overuse is throttled. Read and write operations share the same key but are metered in separate buckets — a flood of GET /payments calls will not consume your capacity to create payments.
Limits apply to your account as a whole, not to individual IP addresses. Spreading requests across many servers or containers does not raise your ceiling.
Limits by environmentAsk
| Environment | Sustained rate | Burst | Scope |
|---|---|---|---|
| Sandbox | 25 req/s | 50 | Per secret key |
| Production (standard) | 100 req/s | 200 | Per secret key |
| Production (scale) | 500 req/s | 1000 | Per secret key |
Webhook delivery, hosted checkout sessions, and the dashboard are not counted against your API budget. Bulk endpoints such as list operations return up to 100 objects per page; paginate with the starting_after cursor rather than polling a single page in a tight loop.
The scale tier is enabled per account, not self-served. If a launch or migration needs headroom, request an increase before the event — see Next steps.
Rate-limit headersAsk
Every response includes headers describing your current budget. Read them on success as well as on 429, so you can throttle proactively instead of reacting to errors.
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Requests left before throttling kicks in |
X-RateLimit-Reset | Unix epoch seconds when the bucket fully refills |
Retry-After | Seconds to wait before retrying (present only on 429) |
A throttled response uses status 429 with a structured body:
{
"error": {
"type": "rate_limit_error",
"code": "too_many_requests",
"message": "Request rate exceeded. Retry after 2 seconds.",
"retry_after": 2
}
}Backoff strategyAsk
When you receive a 429, wait at least the number of seconds in Retry-After, then retry with exponential backoff and jitter. Jitter prevents many clients from retrying in lockstep and re-triggering the limit. The SDK applies this automatically for idempotent operations, but here is the logic if you call the REST API directly.
The SDK retries 429 and 5xx responses transparently, honoring Retry-After. Tune the behavior at construction time:
import { Vinr } from '@vinr/sdk';
const vinr = new Vinr({
secretKey: process.env.VINR_SECRET_KEY,
maxRetries: 5, // attempts after the initial request
retryBaseDelayMs: 250 // grows exponentially with jitter, capped at Retry-After
});
// No special handling needed — throttled calls are retried for you.
const payment = await vinr.payments.create({
amount: 1000,
currency: 'EUR',
returnUrl: 'https://shop.example.com/return',
});async function callWithBackoff(path: string, init: RequestInit, attempt = 0): Promise<Response> {
const res = await fetch(`https://api.vinr.com/v1${path}`, {
...init,
headers: { 'X-Api-Key': process.env.VINR_SECRET_KEY!, ...init.headers },
});
if (res.status !== 429 || attempt >= 5) return res;
const retryAfter = Number(res.headers.get('Retry-After') ?? 1);
const backoff = Math.min(2 ** attempt * 250, retryAfter * 1000);
const jitter = Math.random() * backoff;
await new Promise((r) => setTimeout(r, backoff + jitter));
return callWithBackoff(path, init, attempt + 1);
}Never retry in a tight while loop without delay. Hammering a throttled key keeps the bucket empty, extends your Retry-After, and can trip abuse protection.
Staying under the limitAsk
Read the remaining budget
Inspect X-RateLimit-Remaining on responses and slow down before it reaches zero, rather than waiting for the first 429.
Prefer webhooks over polling
Subscribe to events like payment.completed and invoice.paid instead of polling list endpoints. See Webhooks.
Batch and paginate
Use cursor pagination and request the largest page size you need, instead of issuing many small calls.
Use idempotency keys
Send an idempotencyKey on writes so a safe retry never creates a duplicate pay_ or re_ resource.
Next stepsAsk
API Reference
Endpoints, parameters, and the full error code table.
Webhooks
Replace polling with event-driven delivery to cut request volume.
Errors & troubleshooting
Diagnose 429s and other API errors in production.