Scraping is asynchronous: submit a run → poll → fetch data. See Quick Start for an end-to-end example; this page is the full reference for the envelope, statuses, billing, and errors.
Authentication
Every call carries an API Key in the request header:
Authorization: Bearer YOUR_API_KEY
Get Your API Key
- Log in to AntsData Console
- Go to API Keys → Create New Key
- Copy the key (format
ants_xxxxxxxxxxxx; shown once — save it securely)
Security Best Practices
-
Don't hardcode or commit API Keys to Git; use environment variables; rotate regularly.
export ANTSDATA_API_KEY="ants_xxxxxxxxxxxx" # Linux / macOS $env:ANTSDATA_API_KEY="ants_xxxxxxxxxxxx" # Windows PowerShell
import os, requests API_KEY = os.getenv("ANTSDATA_API_KEY") H = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
Unified Response Envelope
All responses share one envelope; code === 0 means success:
{ "code": 0, "message": "ok", "data": <object> }
code == 0is the only success test (nosuccessboolean).- On error, the matching HTTP status code is returned with
{ "code": <int>, "message": <human-readable> }. - Billing is not on the top-level envelope but on the run object (
chargedCredits/recordCount).
Run Lifecycle
POST /v1/scraper/{platform}/{resource} → 201 { id, status:"queued" } create run (async, holds credits)
→ 200 { status:"succeeded"|"failed" } sync, terminal within timeout
→ 202 { id, status:"queued"|"running"} sync timeout fallback (switch to polling)
GET /v1/scraper/runs/{id} → { status, targetsOk, tasks[], ... } poll until terminal
GET /v1/scraper/runs/{id}/records?page=&pageSize= → paginated records fetch after success
GET /v1/scraper/runs/{id}/download?format=json|csv|excel → attachment download
The submit status code depends on
deliveryMode:asyncis always201;syncreturns200when terminal,202on timeout fallback (see quick-start "Sync mode").
State Machine
queued ──▶ running ──▶ succeeded (terminal)
└──▶ failed (terminal)
- Only 4 statuses:
queued/running/succeeded/failed(no pending/completed/cancelled). - Terminal =
succeeded|failed; stop polling once terminal. - No progress percentage: track progress via
targetCount/targetsOk/targetsFailed.
Run Object Reference
POST (create) and GET /runs/{id} return the same Run object (data):
| Field | Type | Description |
|---|---|---|
id |
int64 | Run number (job id; used by all follow-up queries) |
actorName / endpointName |
string | Platform / endpoint name |
status |
string | queued/running/succeeded/failed |
deliveryMode |
string | sync / async |
recordLimit |
int | Max records per target |
targetCount |
int | Total targets |
targetsOk / targetsFailed |
int | Succeeded / failed targets |
recordCount |
int | Total records produced |
maxCost |
string | Credits held at submission |
chargedCredits |
string | Actual charge ("0" before succeeded) |
createdAt |
string | ISO 8601 |
tasks[] |
array | Per-target results (below) |
deliveries[] |
array | Per-delivery-channel status (when deliveries configured) |
Per-Target Results tasks[]
A run's N targets fan out into N tasks; per-target success/failure lives here (not in the records):
| Field | Description |
|---|---|
targetIndex |
Aligns with the request's targets[] index |
taskId |
Platform-side task id (string) |
submitState |
pending / submitted / submit_failed (dispatch state) |
status |
queued / running / succeeded / failed (execution state) |
recordCount |
Records produced for this target |
errorMessage |
Failure reason (when failed) |
Partial success: as long as ≥1 target succeeds, the run is
succeededand billed by successful records; failed targets each carry anerrorMessageintasks[].
Fetch & Download
- Paginated records:
GET /v1/scraper/runs/{id}/records?page=1&pageSize=20→data = { total, page, pageSize, items:[...] };pageSizedefault 20, max 100; empty until the run succeeds. - Download:
GET /v1/scraper/runs/{id}/download?format=json|csv|excel(default json) → attachment stream. - Results are kept ~90 days — fetch them promptly.
Billing: Hold → Settle / Release
Billed by the number of successfully produced records (one unit price per 1,000 records):
| Point | Action | Fields |
|---|---|---|
| Submit | Hold maxCost = targetCount × ⌈recordLimit/1000⌉ × unit price; insufficient balance → submit rejected (402) |
maxCost |
| Success | Settle chargedCredits = ⌈successful records / 1000 × unit price⌉ (≤ maxCost) |
chargedCredits / recordCount |
| Full failure | Release the hold, no charge | — |
Per-endpoint prices and plans are on the Pricing page.
Error Codes
Unified envelope errors: real HTTP status code + {code, message}.
| Code | Meaning | Action |
|---|---|---|
400 |
Invalid params / targets |
Check the body and target fields |
401 |
Unauthorized / invalid API Key | Check your key |
402 |
Insufficient credits (submit rejected) | Top up |
404 |
Run not found / not yours | Check the run id |
429 |
Rate limited | Slow down, see Retry-After |
500 |
Server error | Retry later |
503 |
Service unavailable | Retry later |
Specific business
codevalues follow the platform errcode (run domain 4000+, insufficient credits, etc.); final values are authoritative on the server.A single failed target does not produce a top-level error: the run can still besucceeded(partial success); failure detail is intasks[].errorMessage.
Retry Strategy
import time
def poll_run(base, headers, run_id, max_wait=300):
waited = 0
for delay in [1, 2, 5, 5, 5, 5, 5, 5, 5, 5]:
r = requests.get(f"{base}/scraper/runs/{run_id}", headers=headers)
if r.status_code >= 500: # server error: back off and retry
time.sleep(delay); waited += delay; continue
run = r.json()["data"]
if run["status"] in ("succeeded", "failed"):
return run
time.sleep(delay); waited += delay
if waited >= max_wait:
raise TimeoutError(f"run {run_id} did not finish within {max_wait}s")
return run
| Situation | Retry? |
|---|---|
running while polling |
✅ Back-off poll (1s→5s) |
429 / 5xx |
✅ Back off and retry (honor Retry-After) |
401 / 400 / 402 |
❌ Fix, then retry |
run failed |
❌ Inspect tasks[].errorMessage; resubmit failed targets if needed |
Best Practices
- Store the API Key in an environment variable, never hardcode it.
- Poll with exponential backoff (1s→2s→5s cap); don't busy-poll; set a total timeout.
- One run, many targets: batching
targetsbeats submitting one-by-one; per-target status is intasks[]. - Set
recordLimitsensibly to cap per-run cost;0(unlimited) computes the hold from available credits. - Use
/downloadfor large results,/recordsfor paged browsing.
Billing & Pricing
Billing is per successfully returned data record: each returned record is charged at that endpoint's unit price; failed records are not charged; the actual charge is reflected in the run's chargedCredits. Per-endpoint prices and plans are on the Pricing page.
Next Steps
- Quick Start — End-to-end submit / poll / fetch example
- Scraper platform guides: TikTok · X · Instagram · Facebook · LinkedIn · Google · YouTube · Amazon
- Synchronous search: Google SERP — results returned in a single call
FAQ
Q: Do API Keys expire?
No fixed expiry; revoke and regenerate anytime in the console. Use different keys for different apps/environments.
Q: Any recommended polling cadence?
Exponential backoff (1s→2s→5s cap); stop at succeeded/failed; avoid busy-polling that triggers rate limits.
Q: Am I charged for failed scrapes?
No. A fully-failed run releases the hold and isn't charged; partial success is billed only by the records produced.
Q: How do I view usage and billing?
Log in to the console Billing page for credit usage and invoices.
Q: How do I contact support?
Submit a ticket in the console, email [email protected], or join our Discord.
