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_credit for credits, ticket_expired and 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.
Example request
curl 'https://www.hashscraper.com/v5/api/datasets/239758028' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Example response 200
{
  "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
  }
}
Example response 402
{
  "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.
Example request
curl 'https://www.hashscraper.com/v5/api/bots/8f3a7c1e9b5d24f6' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Example response 200
{
  "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.

Example response 429
{
  "result": "error",
  "version": "v5",
  "request_id": "req_5a8c1f1c-9b2d-4e7c-9abf-3f7e0a4d1b21",
  "code": "RATE_LIMITED",
  "message": "Rate limit exceeded.",
  "elapsed_sec": 0.0011
}
Python example
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 example
// 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

WhatLimitDetails
One list page (limit)Up to 1,000 itemsPaging through lists
Bulk collection (POST /collections/bulk)Up to 100 itemsCollections
Bulk job (POST /collections/bulk_jobs)Up to 5,000 itemsCollections
Excel input fileUp to 20MBSettings
Plan run lookup rangeUp to 31 daysPlan runs

Related guides