Last verified: October 15, 2026, against the billing code, the caching docs and the batch docs. The plan cards and tables above read from the same plan data that billing enforces, so they change when the plans change.
RenderScreenshot bills in credits. One fresh render uses one credit. It does not matter if the render is a thumbnail, a 1200x630 social card, a full-page capture of a long article or a multi-page PDF. Repeats served from cache, failed renders and rejected requests use no credits. Plans differ only in how many credits you get each month, your rate limit and your batch size. Every rendering feature is in every plan, the Free plan included.
The rest of this page explains what uses a credit, what does not, and how to keep repeat traffic at zero credits. If you want to compare our prices with other vendors, see the screenshot API pricing comparison. It puts ten vendors on one grid and reports the volumes where we are not the cheapest.
What uses a credit
| Request | Credits | Notes |
|---|---|---|
| Screenshot of a URL (PNG, JPEG or WebP) | 1 | Any viewport, device, preset or quality |
| Full-page or element capture | 1 | Page height does not change the price |
PDF (format: pdf) |
1 | Paper size, margins and page count do not change the price |
Render from html or markdown input instead of a URL |
1 | Same price as a URL |
Repeat request with cache.available, found in cache |
0 | Response has cache.hit: true |
Repeat request with cache.available, not in cache |
1 | A normal render that refills the cache |
Request with cache.refresh |
1 | Forces a new render |
| Batch request | 1 per URL that renders | Failed URLs use 0. The batch needs enough credits for all URLs before it starts |
| Render that fails in the browser (target error, timeout) | 0 | You get an error response |
Validation error, bad API key, rate limit (429) |
0 | Rejected before any render |
Image fetched through the X-Cache-URL CDN link |
0 | Served by the CDN, never reaches the API |
| Cache management, presets list, devices list | 0 | Read and admin endpoints |
| Webhook delivery or upload to your own bucket | 0 | Included in the render |
| Render from the dashboard playground | 1 | Uses your account credits like an API call |
Two features depend on the plan, not on credits. Upload to your own S3-compatible bucket is on paid plans only. Batch size and batch concurrency increase with each plan.
Three rules that decide your bill
1. You pay per render, not per second or per byte
Our renderer runs headless Chromium on Cloudflare Browser Rendering, which bills us by browser time. A slow, heavy page costs us more than example.com. We decided that this variance is our problem, not yours. A credit is a credit, so you can predict your bill from your request count alone. If you prefer to pay for raw browser time and build the rest yourself, calling Cloudflare directly is a fair option, and that page shows what you would have to build.
2. Repeats are free only when you ask for the cache
By default, two identical requests make two renders and use two credits. Add cache.available: true (or cache_available=true on a GET) and the API first looks for a successful render from your account with the same rendering parameters inside the cache TTL. A hit returns the stored image at zero credits. A miss renders as normal. The default TTL is 24 hours, and you can set from 1 hour up to 30 days with cache.ttl. Cache entries belong to your account and are never shared with other accounts.
You still need at least one credit left to make a request, because the API cannot know in advance if the request will be a hit or a miss.
3. Credits reset each period, and there is no overage
The Free plan resets on the first day of every month. Paid plans reset when each monthly invoice is paid. Unused credits do not carry over. When you reach zero, requests fail with HTTP 403 and the code insufficient_credits. We do not bill extra usage automatically, so a loop or a crawler storm cannot produce a surprise invoice. If you upgrade in the middle of a period, your allowance goes up to the new plan at once, and the credits you already used still count.
The other side of this rule: a hard stop also stops your screenshots. If your product cannot accept failed captures at the end of a busy month, watch the remaining credits (code below) or choose a plan with headroom.
Keep repeat traffic at zero credits
These three requests do the same thing. Each asks for an og_card render of a page with cache.available set, then reports whether it was a cache hit, the credits it used and the credits left. Run one of them twice: the second run reports a cache hit and zero credits used.
cURL
curl -s -o og.png -D - \ "https://api.renderscreenshot.com/v1/screenshot?url=https%3A%2F%2Fexample.com&preset=og_card&cache_available=true" \ -H "Authorization: Bearer $RS_API_KEY" | grep -iE '^x-(cache|credits)' # X-Cache-Hit, X-Cache-URL, X-Credits-Used and X-Credits-Remaining come back on every image response
Node.js
// credits.mjs (Node 22+). Usage: RS_API_KEY=rs_live_... node credits.mjs https://example.com const target = process.argv[2] ?? 'https://example.com'; const res = await fetch('https://api.renderscreenshot.com/v1/screenshot', { method: 'POST', headers: { Authorization: `Bearer ${process.env.RS_API_KEY}`, 'Content-Type': 'application/json', Accept: 'application/json', // JSON metadata instead of the image bytes }, body: JSON.stringify({ url: target, preset: 'og_card', cache: { available: true } }), }); const body = await res.json(); if (!res.ok) { console.error(`${res.status} ${body.error?.code}: ${body.error?.message}`); process.exit(1); } const { hit, url } = body.cache; const { credits, remaining } = body.usage; console.log(`cache hit: ${hit} | credits used: ${credits} | credits left: ${remaining}`); console.log(`public CDN URL: ${url}`); if (remaining < 100) console.warn('Fewer than 100 credits left in this period.');
Python
# credits.py (Python 3.10+, standard library only). # Usage: RS_API_KEY=rs_live_... python credits.py https://example.com import json import os import sys import urllib.error import urllib.request target = sys.argv[1] if len(sys.argv) > 1 else "https://example.com" payload = {"url": target, "preset": "og_card", "cache": {"available": True}} req = urllib.request.Request( "https://api.renderscreenshot.com/v1/screenshot", data=json.dumps(payload).encode(), headers={ "Authorization": f"Bearer {os.environ['RS_API_KEY']}", "Content-Type": "application/json", "Accept": "application/json", # JSON metadata instead of the image bytes }, method="POST", ) try: with urllib.request.urlopen(req, timeout=60) as res: body = json.load(res) except urllib.error.HTTPError as err: error = json.load(err).get("error", {}) sys.exit(f"{err.code} {error.get('code')}: {error.get('message')}") usage, cache = body["usage"], body["cache"] print(f"cache hit: {cache['hit']} | credits used: {usage['credits']} | credits left: {usage['remaining']}") print(f"public CDN URL: {cache['url']}") if usage["remaining"] < 100: print("Fewer than 100 credits left in this period.", file=sys.stderr)
Checked on October 15, 2026 with Node 22.18 and Python 3.14 against a local stub that returns the documented response shape and the insufficient_credits error. The stub confirms the parsing and the non-zero exit code on errors; it is not a live render.
The value of the 100-credit warning is yours to choose. Put it where your monitoring can see it: a log line, a metric or a message to your team channel.
Where credits leak, and how to stop it
Public image URLs. If you put an API URL in an <img> tag or an og:image meta tag, every fetch by a browser or a social crawler is a new GET request. Without cache_available=true, each fetch is a fresh render. Two fixes:
- Use the
X-Cache-URLlink (orcache.urlin JSON). It is a public CDN link that needs no key, and fetches of it never reach the API, so they use no credits. It stays valid until the cache entry expires. - If you use signed URLs, put
cache_available=truein the query string before you sign it. The signature covers every query parameter. The SDK builders expose cache TTL and refresh but not this flag, so sign the query yourself as the signed URL docs show.
Retries that are not retries. A retry after a 429 or a failed render costs nothing, because neither used a credit. A retry after a timeout in your client can cost a second credit if the first render succeeded on our side. Set cache.available on retries, and the second call returns the first render at zero credits.
Batch loops. A batch uses the same credits as single requests, one per URL. The gain is throughput, not price: one request, your plan's batch concurrency, and an optional webhook when the batch finishes.
When the API costs more than running a browser yourself
At high, steady volume, a self-hosted browser fleet can cost less per screenshot than any per-render plan. The trade is your time: fonts, memory leaks, timeouts, retries, storage and a CDN. Our guide to choosing a screenshot API covers that decision. If your volume is above our largest self-serve plan, email [email protected] before you build anything.
To start, get a free API key. You need no credit card, and the free credits renew every month.