Rate Limits
The PostCapture API enforces rate limits to ensure fair usage and service stability.
Current Limits
The budget belongs to the account, not the key: every key you create draws from the same 10 requests per minute, and so does the legacy endpoint. Ten calls to POST /api/v1/screenshot leave nothing for GET /api/screenshot in the same window.
How It Works
PostCapture uses a fixed-window rate limiter. A 60-second window starts with your first request. After 10 requests within that window, subsequent requests receive a 429 response until the window resets.
Window start: Timestamp of your first request in the current window.
Counter: Incremented with each request. Resets to 1 when the window expires.
Expiry: 60 seconds after window start. Your next request after expiry starts a new window.
429 Response Format
When rate limited, the API returns a JSON body and a Retry-After header (in seconds):
1{2 "error": "Rate limit exceeded",3 "message": "Too many requests. Limit is 10 per minute. Try again in 42s.",4 "retryAfterMs": 420005}
| Header / Field | Description |
|---|---|
| Retry-After | Seconds until the rate limit window resets (HTTP header). |
| retryAfterMs | Milliseconds until the window resets (JSON body, more precise). |
Monthly Quota (402)
The rate limit caps how fast you can call; your plan caps how much you can render. Each successful call uses one screenshot from the monthly quota, which resets on the 1st of each month. When it runs out, calls fail with 402 until the reset or an upgrade — retrying will not help.
1{2 "error": "Monthly screenshot limit reached (500/500). The quota resets on the 1st of each month.",3 "code": "quota_exceeded",4 "exceeded": true,5 "limit": 500,6 "used": 5007}
To avoid being surprised by it, watch the credits block that every successful v1 response carries. The legacy endpoint returns the same 402 status but without the code, limit and used fields.
Best Practices
- •Respect
Retry-After. Wait the specified duration before retrying instead of polling aggressively. - •Implement exponential backoff. If you hit the limit, wait progressively longer between retries (e.g., 1s, 2s, 4s).
- •Batch your work. If you need many screenshots, space requests evenly within the rate limit window.
- •Cache results yourself. Identical requests are not deduplicated: every call renders again and costs a screenshot. Store the PNG on your side rather than re-requesting the same post.
Retry Example
1async function screenshotWithRetry(postUrl, apiKey, maxRetries = 3) {2 for (let attempt = 0; attempt <= maxRetries; attempt++) {3 const res = await fetch("https://postcapture.com/api/v1/screenshot", {4 method: "POST",5 headers: {6 Authorization: `Bearer ${apiKey}`,7 "Content-Type": "application/json",8 },9 body: JSON.stringify({ postUrl }),10 });1112 if (res.ok) return res.json();1314 if (res.status === 429) {15 const retryAfter = parseInt(res.headers.get("Retry-After") || "10");16 console.log(`Rate limited. Retrying in ${retryAfter}s…`);17 await new Promise((r) => setTimeout(r, retryAfter * 1000));18 continue;19 }2021 // 402 means the monthly quota is gone — retrying will not help.22 const { error } = await res.json();23 throw new Error(`API error ${res.status}: ${error}`);24 }2526 throw new Error("Max retries exceeded");27}