credits & pricing
the crawlbrulee api uses credit-based billing. every page we deliver is classified by the engine that delivered the result: http, browser, screenshot, or cache. the engine sets the base cost, the delivered proxy tier applies a multiplier, and generating requested screenshot slices adds one flat credit, and so does a zero data retention request. failures and fully cached results cost 0 — see what counts as a failure for the exact list.
credit rules
credit rules version 2.2.1 · effective October 9, 2026
version 2.2.1 supersedes version 2.2.0, effective October 5, 2026.
the sections in this group are the credit rules referenced by section 4 of the terms and conditions . they govern how requests consume credits. the plan and included credits presented for your purchase, together with the price and billing interval in the checkout or transaction confirmation from our merchant of record, govern your subscription.
credit cost for your requests
the formula is:
credits = ( engine base × proxy multiplier ) + screenshot slicing + zero data retention
each part comes back on every response, in response_meta.usage (see checking your usage).
| delivered engine | base credits | when it applies |
|---|---|---|
http | 1 | plain http fetches and /api/map |
browser | 3 | a browser delivered the result without a screenshot |
screenshot | 5 | a requested viewport or full-page screenshot was delivered |
cache | 0 | the base result was served from cache |
| delivered proxy | multiplier |
|---|---|
basic | ×1 |
advanced | ×5 |
auto is a request preference, not a billed tier. the response reports and bills the proxy that
actually delivered the result. generating a screenshot-slice variant through
actions_after adds a flat 1 credit after
the proxy multiplier, regardless of how many image tiles it contains. failures cost 0 (see
what counts as a failure).
extraction options (markdown, raw_html, links, images, elements), location emulation,
and JavaScript rendering do not add a separate fee. elements adds nothing on a cache hit either.
JavaScript rendering can change the delivered engine from http to browser, and a delivered
screenshot uses the screenshot base instead of the browser base.
in rare cases, we return the requested page result without an optional screenshot. you’re charged
for the engine that actually delivered it — normally browser, not screenshot.
slice credits apply only when a requested slice variant is generated.
examples
| delivered result | cost |
|---|---|
http, basic proxy | 1 credit |
browser, basic proxy | 3 credits |
screenshot, advanced proxy | 25 credits |
screenshot that generates a slice variant, basic proxy | 6 credits |
screenshot with a slice variant and zero_data_retention, basic | 7 credits |
| cache hit with no newly produced slices | 0 credits |
a 404 page, http, basic proxy | 1 credit |
a 503 page from the site (any engine or proxy) | 0 credits |
an error from us (for example antibot_blocked) | 0 credits |
zero data retention
a request sent with zero_data_retention: true adds a flat 1 credit after the proxy multiplier
when its result is billed and fresh. a cache hit adds 0, and so does a page we don’t bill. in an
organization where we turned it on for every request, the same rule applies to every request,
whether or not it sends the field. the option is off until we enable it for your organization;
ask us at sales@crawlbrulee.com.
what counts as a failure
a failure costs 0 credits. there are two kinds.
1. an error from us. we answer with a non-2xx status and no page, so nothing is billed. this covers every error on the errors page, including:
- the site’s anti-bot protection blocked us (
403 antibot_blocked) - we could not reach the site at all (
502 target_unreachable) - the site redirected in a loop (
422 too_many_redirects) - the site rate-limited us (
429 too_many_requestswithlimited_by: "target_site") - the page was too large or too complex, or its content type isn’t supported (
422 page_too_large,415 unsupported_content,422 unsupported_screenshot_output) - something went wrong on our side (
500,503) - the request itself was rejected (
400,401,429for your own limits)
2. a page the site served with a status we don’t bill. when the site sends back a page, you get
a 200 with that page and its status in page_status_code. we bill it by that status:
page_status_code | billed? | why |
|---|---|---|
2xx | yes | the page you asked for |
404, 410 | yes | the site’s real answer: the page is not there |
401, 402 | yes | the site’s real answer: log-in or payment needed |
any other 4xx (for example 400, 405) | yes | the site’s real answer to that url |
403 | no | often a block, even when it isn’t seen as one |
451 | no | a legal block that can depend on the country you scrape from |
407, 408, 429 | no | the page didn’t really get through |
5xx | no | the site failed; asking again later may give a real page |
in short: we bill 2xx and 4xx pages, except 403, 407, 408, 429 and 451. we never bill
5xx pages. a page we don’t bill still comes back with its content, and every *_credit_cost field in its
response_meta.usage is 0 (engine, proxy and proxy_multiplier still show what ran).
the rest of the billing rules apply as usual: a billed page costs what the engine and proxy that delivered it cost, whatever its status.
screenshot slices
slicing a full-page screenshot (via actions_after)
adds a flat 1 credit when the requested slice variant is generated, regardless of how
many image tiles it contains. the primary screenshot is covered by the screenshot engine base.
slices are computed at response time, so they can also be produced from a cached base result:
| scenario | credits |
|---|---|
| fresh screenshot, no slicing | screenshot base × proxy |
| fresh screenshot that generates a slice variant | screenshot base × proxy + 1 |
| cache hit that generates a new slice variant | 1 |
| cache hit where the requested slice variant is cached | 0 |
| cache hit, no slicing requested | 0 |
the billed slice variant is keyed on the slice request. asking for a slice height you have not used before charges for the new slices the first time; later cache hits are free when those slices are already cached.
concurrency limits
your org has a maximum number of simultaneous active scrapes. the pool is shared between sync and async requests.
when you exceed it, you’ll get a 429:
{
"name": "usage_allocation_error",
"message": "Concurrency limit exceeded",
"details": {
"error_name": "usage_allocation_error",
"reason": "concurrency_limit",
"details": {
"current_concurrent": 5,
"max_concurrent": 5
}
}
}async jobs handle this gracefully — a submitted job waits in pending until a concurrency slot opens, then executes automatically.
checking your usage
per-request: response_meta.usage
this is the customer-facing indicator of what a single request cost — don’t try to derive it
yourself from the proxy tier and add-ons. the service usage ledger determines your balance, subject
to correction of manifest errors. 200 responses from /api/scrape and /api/map, async status
and result responses once the job is done, and successful scrape.complete webhooks carry a
response_meta.usage object. it shows the total and each part of the price:
{
"response_meta": {
"usage": {
"total_credit_cost": 1,
"engine_credit_cost": 1,
"proxy_multiplier": 1,
"screenshot_slicing_credit_cost": 0,
"zero_data_retention_credit_cost": 0,
"engine": "http",
"proxy": "basic"
}
}
}| field | type | description |
|---|---|---|
total_credit_cost | integer | credits charged for this request. always engine_credit_cost × proxy_multiplier + screenshot_slicing_credit_cost + zero_data_retention_credit_cost |
engine_credit_cost | integer | the engine base charged: 1 for http, 3 for browser, 5 for screenshot, 0 for cache. 0 when the page isn’t billed |
proxy_multiplier | integer | 1 for basic, 5 for advanced. always shown, even when the engine cost is 0 |
screenshot_slicing_credit_cost | integer | 1 when screenshot slices were made on this request, otherwise 0. 0 when the page isn’t billed |
zero_data_retention_credit_cost | integer | 1 when the request used zero data retention and the result was billed and fresh, otherwise 0 |
engine | string | delivered billing engine — http, browser, screenshot, or cache |
proxy | string | proxy tier that delivered the result — basic or advanced, never auto |
credits | integer | deprecated — use total_credit_cost, which always has the same value |
screenshot_slices | integer | deprecated — use screenshot_slicing_credit_cost, which always has the same value |
credits and screenshot_slices are deprecated and will be removed in a future version. they
still come back on every response for now, with the same values as total_credit_cost and
screenshot_slicing_credit_cost. switch your code to the new names. if you also read older
responses that don’t have the new fields yet, fall back to the old ones.
a few examples of the breakdown:
| delivered result | engine_credit_cost | proxy_multiplier | screenshot_slicing_credit_cost | total_credit_cost |
|---|---|---|---|---|
http, basic | 1 | 1 | 0 | 1 |
browser, advanced (also for a 404 page) | 3 | 5 | 0 | 15 |
screenshot with new slices, advanced | 5 | 5 | 1 | 26 |
| cache hit that makes new slices | 0 | 1 or 5 | 1 | 1 |
a 503 page from the site, browser, advanced | 0 | 5 | 0 | 0 |
/api/map has no screenshots, so its usage leaves out screenshot_slicing_credit_cost and
screenshot_slices. its engine is http for fresh discovery or cache for a cached result.
aggregate: GET /api/usage
GET /api/usage returns a snapshot of the current billing cycle — credits, quota, and your concurrency limit. useful for pre-flight balance checks, dashboards, and alerting.
curl https://api.crawlbrulee.com/api/usage \
-H "Authorization: Bearer $CRAWLBRULEE_API_KEY"Response:
{
"total_credits": 20000,
"used_credits": 3425,
"available_credits": 16575,
"used_quota_percent": 17.1,
"max_concurrency": 5,
"usage_reset": "2026-07-17T00:00:00.000Z"
}| field | type | description |
|---|---|---|
total_credits | integer | credits available this billing cycle (plan base + purchased + gifted) |
used_credits | integer | credits spent so far this cycle |
available_credits | integer | max(0, total_credits - used_credits); reads 0 once the allowance is spent |
used_quota_percent | number | percent of total_credits used, to 1 decimal |
max_concurrency | integer | maximum simultaneous active scrapes for your org |
usage_reset | string | iso 8601 utc time when the cycle ends and used_credits resets |
the crawlbrulee sdk exposes this as client.usage().
credit exhaustion
when you run out of credits:
- requests fail with a
credit_limiterror (http 429), and keep failing until the cycle resets. - to keep going before the reset, upgrade your plan or buy a credit pack.
{
"name": "usage_allocation_error",
"message": "Credit limit exceeded",
"details": {
"error_name": "usage_allocation_error",
"reason": "credit_limit",
"details": {
"current_usage": 750,
"current_reserved": 0,
"max_credits": 750
}
}
}optimizing credit usage
a few ways to keep costs down:
- use caching — the default 2-day freshness window makes fully cached repeat requests free. a newly produced screenshot-slice variant costs +1. don’t set
max_age: 0unless you need fresh data. - use the lightest engine that fits — http is 1 credit, browser is 3, and screenshot is 5 before the proxy multiplier. request JavaScript or screenshots only when the output needs them.
- start with
basic—advancedmultiplies the engine base by 5. use it when the higher success rate is worth the cost, or useautofor general-purpose scrapers. - normalize your urls — every non-tracking query param is part of the cache key, so dropping the ones that don’t change the page widens your cache hits.
- avoid
actions_beforewhen possible — it makes requests ineligible for cache hits, so every call costs full price.cleanupis different:ads_and_popupsandexclude_selectorsare part of the cache key, so keep them the same from call to call and repeat requests still hit the cache. - batch with async — async jobs wait for concurrency slots automatically instead of failing.
the fastest way to cut costs: check if you’re re-scraping urls within 2 days. if so, the cache is already saving you credits automatically.