Appearance
Error Codes Reference Fresh
DataForSEO uses two layers of error reporting: HTTP status codes at the transport level, and DataForSEO-specific status codes inside the JSON response body.
HTTP Status Codes
| Code | Meaning | Most Common Cause | Fix |
|---|---|---|---|
200 | OK | Request processed | Check the body's status_code for task-level errors |
400 | Bad Request | Malformed JSON, missing required field | Validate your payload structure |
401 | Unauthorized | Wrong credentials | Recheck login/password from the dashboard |
403 | Forbidden | Account suspended or feature not enabled | Check account status |
404 | Not Found | Wrong endpoint URL | Check the endpoint path against the reference |
429 | Too Many Requests | Rate limit hit | Add delays between requests; check /appendix/user/data |
500 | Internal Server Error | DataForSEO server issue | Retry after 60 seconds; contact support if persistent |
503 | Service Unavailable | Maintenance or overload | Retry with exponential backoff |
DataForSEO Status Codes
These appear inside the JSON response body, at both the response level and the individual task level. Always check both.
Success Codes
| Code | Meaning | Notes |
|---|---|---|
20000 | OK | Request successful |
20100 | Task created | Task accepted for processing (task-based endpoints) |
Client Error Codes (4xxxx)
| Code | Meaning | Common Cause | Fix |
|---|---|---|---|
40000 | Bad request | Generic bad request | Inspect the status_message field for details |
40001 | Authentication required | No credentials sent | Include Authorization header |
40002 | Insufficient credits | Account balance too low | Add credits to account |
40004 | Request entity too large | Payload exceeds size limit | Split into smaller batches |
40005 | Not found | Resource ID doesn't exist | Check task ID; task may have expired (30 day TTL) |
40010 | Item not found | No results for the query | Normal for rare keywords; not an error |
40100 | Bad token | Invalid auth credentials | Verify login and password are correct |
40101 | Access denied | Account lacks permission for this endpoint | Check plan includes this API section |
40102 | IP blocked | Your IP is rate-limited or blocked | Rotate IPs or contact support |
40200 | Invalid field | Wrong field name or value | Check field names against API docs |
40201 | Missing required field | Payload is missing a required key | Check the required fields for this endpoint |
40202 | Invalid value | Field value is out of valid range | Check min/max values (e.g., depth cannot exceed 700) |
40400 | Task expired | Task result older than 30 days | Re-run the task |
40401 | Task in progress | Task not yet complete | Poll tasks_ready before fetching with task_get |
40500 | Too many tasks | Batch size exceeds limit | Limit to 100 tasks per request |
40600 | Duplicate task | Same task already submitted | DataForSEO deduplicates within a short window |
Server Error Codes (5xxxx)
| Code | Meaning | Action |
|---|---|---|
50000 | Internal error | Retry after 60 seconds |
50001 | Temporary server issue | Retry up to 3 times with backoff |
50002 | Endpoint temporarily unavailable | Retry; check DataForSEO status page |
Reading Error Responses
Every response has the same structure. Check status_code at two levels:
python
response = requests.post(endpoint, auth=auth, json=payload)
data = response.json()
# Level 1: overall request status
if data["status_code"] != 20000:
print(f"Request error: {data['status_code']} — {data['status_message']}")
# Level 2: per-task status
for task in data.get("tasks", []):
if task["status_code"] not in (20000, 20100):
print(f"Task error: {task['status_code']} — {task['status_message']}")
print(f" Task data: {task.get('data')}")
# Level 3: per-result status (some endpoints)
for result in task.get("result", []):
if isinstance(result, dict) and result.get("status_code"):
if result["status_code"] != 20000:
print(f"Result error: {result['status_code']}")Common Error Patterns and Fixes
40101 — Access Denied on a Specific Endpoint
{"status_code": 40101, "status_message": "Access denied."}Your account plan doesn't include this API section. DataForSEO separates SERP, Backlinks, Labs, and other sections into plan tiers. Check your dashboard under API Access to see which sections are active.
40201 — Missing Required Field
{"status_code": 40201, "status_message": "keyword field is required"}Check the required fields for the endpoint. Most SERP endpoints require at minimum keyword and location_code. Backlink endpoints require target.
40202 — Invalid Value (depth)
{"status_code": 40202, "status_message": "depth value is invalid"}The depth parameter has a maximum value (700 for organic SERP). Values must also be multiples of 10 on some endpoints. Use 10, 20, 50, 100, or 700.
40401 — Task Not Ready
This is not an error — it means you called task_get before the task finished. Always poll tasks_ready first:
python
# Wrong — calling task_get immediately
response = requests.get(f"...task_get/{task_id}", auth=auth)
# Right — poll tasks_ready first
while True:
ready_data = requests.get("...tasks_ready", auth=auth).json()
ready_ids = [r["id"] for task in ready_data["tasks"] for r in task.get("result", [])]
if task_id in ready_ids:
break
time.sleep(30)
# Now safe to fetch
response = requests.get(f"...task_get/{task_id}", auth=auth)40005 — Task Not Found (Expired)
Tasks expire after 30 days. If you try to fetch a result after 30 days, you'll get 40005. Re-run the task if you need fresh data.
429 — Rate Limit
HTTP 429 Too Many RequestsYou're sending too many requests too fast. Add a delay between batches:
python
import time
for batch in batches:
response = requests.post(endpoint, auth=auth, json=batch)
time.sleep(1) # 1 second between batchesCheck your current rate limits:
python
response = requests.get(
"https://api.dataforseo.com/v3/appendix/user/data",
auth=auth
)
print(response.json())Error Handling Template
Use this pattern in all production scripts:
python
import time
import logging
def safe_post(auth, endpoint, payload, max_retries=3):
"""POST with retry logic for transient errors."""
retryable_status = {50000, 50001, 50002}
for attempt in range(max_retries):
try:
response = requests.post(
f"https://api.dataforseo.com/v3/{endpoint}",
auth=auth,
json=payload,
timeout=30
)
# Handle HTTP errors
if response.status_code == 429:
wait = 60 * (attempt + 1)
logging.warning(f"Rate limited. Waiting {wait}s...")
time.sleep(wait)
continue
if response.status_code >= 500:
wait = 30 * (attempt + 1)
logging.warning(f"Server error {response.status_code}. Retry in {wait}s...")
time.sleep(wait)
continue
data = response.json()
# Handle DataForSEO-level errors
if data["status_code"] in retryable_status:
wait = 30 * (attempt + 1)
logging.warning(f"DataForSEO error {data['status_code']}. Retry in {wait}s...")
time.sleep(wait)
continue
return data
except requests.exceptions.Timeout:
logging.warning(f"Request timeout on attempt {attempt + 1}")
time.sleep(10)
raise Exception(f"Failed after {max_retries} attempts")