Rate Limits

The PostCapture API enforces rate limits to ensure fair usage and service stability.

Current Limits

10
per minute
Requests
60
seconds
Window
Per
user
Scope

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": 42000
5}
Header / FieldDescription
Retry-AfterSeconds until the rate limit window resets (HTTP header).
retryAfterMsMilliseconds 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": 500
7}

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 });
11
12 if (res.ok) return res.json();
13
14 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 }
20
21 // 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 }
25
26 throw new Error("Max retries exceeded");
27}