Credits & rate limits V5 (beta)
When and how many credits a collection uses, and how many requests you can send per minute.
Collecting through the API uses credits by the same rules as collecting on screen. v5 requests that read or change bots, settings, plans and datasets cost nothing; credits are used only when a collection saves data.
How credits are used
- By default, the bot's credits per item for every row. Each row of data a collection saves uses that bot's credits per item. By the basic calculation, a bot at 5 credits per item that collects 1,280 rows uses 6,400 credits. Some collection methods that save many rows in one batch can apply a multiplier, and accounts with a contract price use that price.
- No rows, no credits. A collection that saves no data uses no credits. The reason is in the dataset's
empty_reason. - Rows dropped as duplicates cost nothing. Rows judged to be duplicates and not saved use no credits. What counts as a duplicate depends on how the bot collects — saving the same content again in a new collection can use credits again.
- Your balance is checked at the start. Without enough credits the collection does not start and you get
402 CREDIT_EXHAUSTED; with an expired service ticket,402 TICKET_EXPIRED. A plan skips that run and records the reason in Plan runs —insufficient_creditfor credits,ticket_expiredand similar for service tickets. - The dataset has the real amount. The credits used by a dataset are in its
credits_used(an integer). If a collection was appended to an earlier dataset with the stacking setting (union), this value is the total for everything stacked in that dataset, not just the latest collection.
curl 'https://www.hashscraper.com/v5/api/datasets/239758028' \
-H 'Authorization: Bearer YOUR_API_KEY'{
"result": "success",
"version": "v5",
"request_id": "req_5a8c1f1c-9b2d-4e7c-9abf-3f7e0a4d1b21",
"elapsed_sec": 0.0123,
"data": {
"id": 239758028,
"bot": {
"id": "8f3a7c1e9b5d24f6",
"name": "Sample shopping bot"
},
"status": "done",
"data_count": 1280,
"union": false,
"trigger": "api",
"credits_used": 6400,
"created_at": "2026-10-04T09:00:02+09:00",
"updated_at": "2026-10-04T09:00:02+09:00",
"plan": null,
"inputs": {
"param1": {
"name": "Keyword",
"value": "sneakers"
}
},
"started_at": "2026-10-04T09:00:02+09:00",
"elapsed_sec": 94
}
}{
"result": "error",
"version": "v5",
"request_id": "req_5a8c1f1c-9b2d-4e7c-9abf-3f7e0a4d1b21",
"code": "CREDIT_EXHAUSTED",
"message": "Credits exhausted.",
"elapsed_sec": 0.0011
}Credits per item differ by bot
Credits per item differ by bot. Light data such as lists and comments costs less; detailed data or data with AI analysis costs more.
Prices can change, so this guide does not list numbers. These two places always show the current value.
- Bot catalog — see the credits per item of every public bot without signing up.
GET /v5/api/bots,GET /v5/api/bots/{bot_id}—credit_per_item(an integer) in the response.
curl 'https://www.hashscraper.com/v5/api/bots/8f3a7c1e9b5d24f6' \
-H 'Authorization: Bearer YOUR_API_KEY'{
"result": "success",
"version": "v5",
"request_id": "req_5a8c1f1c-9b2d-4e7c-9abf-3f7e0a4d1b21",
"elapsed_sec": 0.0123,
"data": {
"id": "8f3a7c1e9b5d24f6",
"name": "Sample shopping bot",
"description": "Collects product lists by keyword.",
"schedule_group": null,
"union": false,
"created_at": "2026-08-12T10:21:05+09:00",
"updated_at": "2026-10-01T18:02:40+09:00",
"worker_source": {
"name": "Shopping list",
"version": 12,
"updated_at": "2026-09-20T11:00:00+09:00",
"price": 5
},
"credit_per_item": 5,
"plans_count": 2,
"active_plans": [
{
"id": "512",
"name": "주기 2",
"label": "매주 월요일 09:00 (KST)"
}
],
"period_summary": "주기 2 · 매주 월요일 09:00 (KST)",
"next_run_at": "2026-10-05T09:00:00+09:00",
"latest_dataset": {
"id": 239758028,
"status": "done",
"data_count": 1280,
"trigger": "period",
"created_at": "2026-09-28T09:00:03+09:00"
}
}
}Rate limits
Requests are accepted up to 150 per minute per IP address. The count is by the IP address the requests come from, not by API key — several programs on the same server share one count.
Above that you get HTTP 429. The body is the same JSON as other errors, with code RATE_LIMITED. The Retry-After response header holds the number of seconds left before you may send again — wait that long, then send again. When you resend a request that starts a collection, use the same idempotency key.
This number is a guideline, not a guarantee. A few more requests may get through at times, but do not rely on it. Checking a collection every 10 to 30 seconds is enough, and to run many bots at once use bulk collection instead of one call per bot.
{
"result": "error",
"version": "v5",
"request_id": "req_5a8c1f1c-9b2d-4e7c-9abf-3f7e0a4d1b21",
"code": "RATE_LIMITED",
"message": "Rate limit exceeded.",
"elapsed_sec": 0.0011
}import time
import uuid
import requests
def request_with_backoff(method, url, headers=None, **kwargs):
headers = dict(headers or {})
if method != "GET":
# one key per request — every retry of the same request reuses it
headers.setdefault("Idempotency-Key", str(uuid.uuid4()))
for attempt in range(8):
res = requests.request(method, url, headers=headers, timeout=30, **kwargs)
if res.status_code != 429 or attempt == 7:
return res
# a 429 carries Retry-After: the seconds left until the one-minute window resets
retry_after = res.headers.get("Retry-After", "")
time.sleep(int(retry_after) if retry_after.isdigit() else min(2 ** attempt, 30))
// Node.js 18+ (built-in fetch)
const { randomUUID } = require("node:crypto");
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function requestWithBackoff(method, url, { headers = {}, body } = {}) {
const h = { ...headers };
// one key per request — every retry of the same request reuses it
if (method !== "GET") h["Idempotency-Key"] ??= randomUUID();
if (body) h["Content-Type"] = "application/json";
for (let attempt = 0; ; attempt++) {
const res = await fetch(url, { method, headers: h, body: body ? JSON.stringify(body) : undefined });
if (res.status !== 429 || attempt === 7) return res;
// a 429 carries Retry-After: the seconds left until the one-minute window resets
const wait = Number(res.headers.get("Retry-After")) || Math.min(2 ** attempt, 30);
await sleep(wait * 1000);
}
}
How much one request can handle
| What | Limit | Details |
|---|---|---|
One list page (limit) | Up to 1,000 items | Paging through lists |
Bulk collection (POST /collections/bulk) | Up to 100 items | Collections |
Bulk job (POST /collections/bulk_jobs) | Up to 5,000 items | Collections |
| Excel input file | Up to 20MB | Settings |
| Plan run lookup range | Up to 31 days | Plan runs |