Idempotency key V5 (beta)
Keep a collection from running twice, or credits from being charged twice, when you resend after a dropped connection.
If the connection drops while the response to a start request is on its way, your program sees a failure and sends the same request again. The same collection can then run twice and credits can be charged twice. With the Idempotency-Key header you say "this is a resend of the earlier request", and the server returns the first response instead of starting anything new.
A new key for every request
A key is a string of 1 to 255 characters. A UUID is recommended ($(uuidgen)). Responses are kept for 24 hours per (account, key) pair.
Do not use a fixed key. Create a new key for each request (one collection intent) and reuse a key only when you resend the same request after a dropped connection. If you put the same string in every request, collections from the second one on do not start: you either get the first response again (same content) or 422 IDEMPOTENCY_KEY_REUSED (different content).
The six places where it is required
A POST that creates something or uses credits requires the key. Without it you get 400 IDEMPOTENCY_KEY_REQUIRED.
Other requests (changing settings or plans, deleting, reading, preview, copy_settings, cancel, notifications/test) give the same result when sent twice or create nothing, so the key is not required. If you send one, the same rules apply. Every write example in this guide includes a key.
| Path | Why |
|---|---|
POST /v5/api/bots/{bot_id}/collections | Starting a collection: credits |
POST /v5/api/plans/{plan_id}/run | Running a plan now: credits |
POST /v5/api/collections/bulk | Collecting several bots: credits |
POST /v5/api/collections/bulk_jobs | Accepting a bulk job: twice means two jobs, credits |
POST /v5/api/bots/{bot_id}/plans | Creating a plan: sending twice makes two plans |
POST /v5/api/datasets/{dataset_id}/retry | Retry: runs the collection again |
curl -X POST 'https://www.hashscraper.com/v5/api/bots/8f3a7c1e9b5d24f6/collections' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{
"inputs": {
"param1": "sneakers"
}
}'{
"result": "error",
"version": "v5",
"request_id": "req_5a8c1f1c-9b2d-4e7c-9abf-3f7e0a4d1b21",
"code": "IDEMPOTENCY_KEY_REQUIRED",
"message": "Idempotency-Key header is required.",
"elapsed_sec": 0.0011,
"details": {
"hint": "Send a unique `Idempotency-Key` header (1-255 characters, e.g. a UUID) per request; reuse it only to retry the same request."
}
}Behavior
| Case | Response |
|---|---|
| A key we have not seen | Processed normally, and the response is kept for 24 hours. |
| Same key, same content (path and body) | Nothing new starts; the kept first response is returned unchanged with the header Idempotent-Replayed: true. |
| Same key, different content | 422 IDEMPOTENCY_KEY_REUSED: this catches using a fixed key by mistake. |
| The first request is still being processed | 409 REQUEST_IN_PROGRESS: send again with the same key shortly. |
| The first request ended with a 4xx (bad input, not enough credits and so on) | The error response is kept too, and the same key gets the same error. After fixing the input or topping up credits, send with a new key. |
| The first request ended with a 5xx (server error) | It is not kept. Sending the same key again is processed anew. If the error happened after the collection was already handed off, that response is kept so the collection is not started again. |
{
"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": "running",
"data_count": 0,
"union": false,
"trigger": "api",
"credits_used": 0,
"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": 3,
"saved_to_settings": true,
"plans_affected": []
}
}{
"result": "error",
"version": "v5",
"request_id": "req_5a8c1f1c-9b2d-4e7c-9abf-3f7e0a4d1b21",
"code": "IDEMPOTENCY_KEY_REUSED",
"message": "Idempotency-Key was already used with a different request.",
"elapsed_sec": 0.0011
}{
"result": "error",
"version": "v5",
"request_id": "req_5a8c1f1c-9b2d-4e7c-9abf-3f7e0a4d1b21",
"code": "REQUEST_IN_PROGRESS",
"message": "The first request with this Idempotency-Key is still in progress.",
"elapsed_sec": 0.0011
}Retry code example
Create the key once inside the function and reuse that same key inside the retry loop.
import time
import uuid
import requests
BASE = "https://www.hashscraper.com/v5/api"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}
def start_collection(bot_id, inputs):
# 이 수집 요청 한 번에 쓸 키 — 요청마다 새로 만들고, 재시도에서만 같은 키를 다시 쓴다
key = str(uuid.uuid4())
for attempt in range(5):
try:
res = requests.post(
f"{BASE}/bots/{bot_id}/collections",
headers={**HEADERS, "Idempotency-Key": key},
json={"inputs": inputs},
timeout=30,
)
except requests.exceptions.RequestException:
time.sleep(2 ** attempt) # 응답을 못 받았다 — 같은 키로 다시 보낸다
continue
body = res.json()
if res.status_code == 409 and body.get("code") == "REQUEST_IN_PROGRESS":
time.sleep(2) # 첫 요청이 아직 처리 중 — 잠시 뒤 같은 키로
continue
return body
raise RuntimeError("could not confirm the request")