Complete reference for the BotAnon proxy API
https://www.botanon.com/api/fetch?url=...&apikey=...https://www.botanon.com/api/fetch (JSON body)Both methods accept the same parameters. Use POST when sending custom headers or complex POST data.
Every request requires an API key. Contact [email protected] to get a key. You can supply the key in any of these ways (the apikey parameter takes precedence if more than one is present):
1. As a query / body parameter (apikey):
GET /api/fetch?url=https://example.com&apikey=YOUR_KEY
2. As an X-API-Key request header:
GET /api/fetch?url=https://example.com
X-API-Key: YOUR_KEY
3. As a Bearer token in the Authorization header:
GET /api/fetch?url=https://example.com
Authorization: Bearer YOUR_KEY
| Parameter | Description |
|---|---|
| url | Target URL to fetch (http:// or https://) |
| apikey | Your API key (or send via X-API-Key / Authorization: Bearer header instead — see Authentication) |
| Parameter | Default | Description |
|---|---|---|
| method | GET | HTTP method on target: GET or POST |
| post_data | Request body for POST requests | |
| post_type | form | POST encoding: form or json |
| headers | Custom headers to forward to target (object, POST JSON only) | |
| ua | Chrome 131 | User-Agent, or random for random modern browser UA |
| timeout | 30 | Timeout in seconds (max 120) |
| tag | Label for log filtering (e.g. seo_audit) | |
| originator_ip | End-user IP for per-user rate limiting |
| Parameter | Default | Description |
|---|---|---|
| proxy | auto | Force proxy: eris, zyte, scrapeowl, scrapedo, residential, browser, browser_fetch, scamadviser_cf, trustpilot_cf, none. Comma-separate for a chain: browser_fetch,scrapeowl. |
| render_js | 0 | JS rendering via ScrapeOwl/Zyte (cheaper than browser) |
| premium | 0 | ScrapeOwl premium proxies |
| browser | 0 | Full headless browser via Cloudflare |
| country | Geo-target: ISO code (e.g. US, GB) |
| Parameter | Default | Description |
|---|---|---|
| refresh | 0 | Bypass cache, fetch fresh |
| cache | Override cache TTL in days (1–365) | |
| compress | 0 | Gzip-compressed response |
Forward API keys, auth tokens, etc. to the target via POST JSON body:
cURLcurl -s -X POST https://www.botanon.com/api/fetch \
-H 'Content-Type: application/json' \
-d '{
"url": "https://api.bing.microsoft.com/v7.0/search?q=test",
"apikey": "YOUR_KEY",
"headers": {"Ocp-Apim-Subscription-Key": "YOUR_BING_KEY"},
"tag": "bing"
}' | jq .
Python
resp = requests.post("https://www.botanon.com/api/fetch", json={
"url": "https://api.bing.microsoft.com/v7.0/search?q=test",
"apikey": "YOUR_KEY",
"headers": {"Ocp-Apim-Subscription-Key": "YOUR_BING_KEY"},
"tag": "bing",
})
PHP
$ch = curl_init('https://www.botanon.com/api/fetch');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://api.bing.microsoft.com/v7.0/search?q=test',
'apikey' => 'YOUR_KEY',
'headers' => ['Ocp-Apim-Subscription-Key' => 'YOUR_BING_KEY'],
'tag' => 'bing',
]),
]);
{
"success": true,
"request_id": "a7c3e9f2-4b1d-4e8a-9f2c-1a2b3c4d5e6f",
"url": "https://example.com",
"final_url": "https://www.example.com/",
"http_code": 200,
"body": "<!doctype html>\n<html>...",
"body_size": 45230,
"body_compressed_size": 8102,
"headers": {
"Content-Type": "text/html; charset=UTF-8",
"Server": "nginx"
},
"timing": {
"total_ms": 1230.5,
"connect_ms": 150.2,
"ssl_ms": 80.1,
"transfer_ms": 320.0
},
"proxy": {
"driver": "eris",
"name": "Eris Worker",
"proxy_id": 1
},
"retries": 0,
"cached": false,
"cache_expires": "2026-04-18 14:30:00",
"compressed": false,
"customer": {
"requests_today": 142,
"success_today": 140,
"failures_today": 2,
"requests_this_month": 4235
}
}
{
"success": false,
"error_code": "ALL_PROXIES_FAILED",
"error": "All proxies failed",
"url": "https://blocked-site.com",
"http_code": 403,
"last_proxy_error": "HTTP 403",
"last_proxy_driver": "residential",
"retries": 3,
"proxies_tried": ["eris", "zyte", "scrapeowl", "residential"]
}
{
"success": false,
"error_code": "RATE_LIMIT_CUSTOMER",
"error": "Customer rate limit exceeded: 1001/1000 requests per hour",
"limit": 1000,
"current": 1001,
"retry_after": 60
}
| Field | Type | Description |
|---|---|---|
| success | bool | Always true |
| request_id | string | Unique UUID for log correlation |
| url | string | The URL you requested |
| final_url | string | URL after redirects |
| http_code | int | HTTP status from target (200, 301, 404, etc.) |
| body | string | Full response body. Text (HTML/JSON/XML) is returned verbatim. Binary content (images, PDFs, etc.) is base64-encoded — see body_encoding. |
| body_encoding | string | Present only when body is base64-encoded: value "base64". Absent for normal UTF-8 text responses. When present, base64-decode body to recover the exact bytes. |
| body_size | int | Size of the decoded body in bytes (the true byte count, not the length of the base64 string) |
| body_compressed_size | int | Decoded body size after gzip |
| headers | object | Response headers from target |
| timing.total_ms | float | Total request time (ms) |
| timing.connect_ms | float | TCP connection time |
| timing.ssl_ms | float | SSL handshake time |
| timing.transfer_ms | float | Time to first byte |
| proxy.driver | string | Proxy used: eris, zyte, scrapeowl, residential, browser, none |
| proxy.name | string | Human-readable proxy name |
| proxy.proxy_id | int | Internal proxy ID |
| retries | int | Proxies that failed before success (0 = first try) |
| cached | bool | true if from cache (timing = 0) |
| cache_expires | string | Cache expiry datetime |
| compressed | bool | true if gzip-compressed |
| customer.requests_today | int | Your requests today |
| customer.success_today | int | Successful today |
| customer.failures_today | int | Failed today |
| customer.requests_this_month | int | Requests this month |
| Field | Type | Description |
|---|---|---|
| success | bool | Always false |
| error_code | string | Machine-readable error code |
| error | string | Human-readable message |
| http_code | int | HTTP code from last proxy (0 if connection failed) |
| last_proxy_error | string | Last proxy's error message |
| last_proxy_driver | string | Last proxy driver tried |
| retries | int | Total proxies tried |
| proxies_tried | array | Drivers attempted, in order |
| limit | int | Rate limit threshold (429 only) |
| current | int | Current count in window (429 only) |
| retry_after | int | Seconds to wait (429 only) |
json.loads(data["body"]) (Python), json_decode($data['body'], true) (PHP), JSON.parse(data.body) (JS).| Code | HTTP | Meaning |
|---|---|---|
| MISSING_URL | 400 | No url parameter |
| MISSING_APIKEY | 401 | No apikey parameter |
| INVALID_APIKEY | 403 | Key not found or customer inactive |
| INVALID_URL | 400 | Malformed, not http/https, or private IP |
| INVALID_TIMEOUT | 400 | Exceeds customer max |
| INVALID_METHOD | 400 | Not GET or POST |
| INVALID_PROXY | 400 | Unknown proxy driver |
| RATE_LIMIT_CUSTOMER | 429 | Hourly limit exceeded |
| RATE_LIMIT_CALLER_IP | 429 | Caller IP limit exceeded |
| RATE_LIMIT_ORIGINATOR_IP | 429 | Originator IP limit exceeded |
| RATE_LIMIT_DAILY | 429 | Daily limit exceeded |
| ALL_PROXIES_FAILED | 502 | Every proxy failed |
| PROXY_ERROR | 502 | Proxy returned non-2xx |
| INTERNAL_ERROR | 500 | Server error |
Default failover chain (configurable per-customer):
| # | Driver | Type | Best For |
|---|---|---|---|
| 1 | eris | CF Worker | Most sites, free, fast |
| 2 | zyte | Premium API | Anti-bot, CAPTCHAs |
| 3 | scrapeowl | Premium API | JS rendering, premium proxies |
| 4 | residential | Residential IPs | Datacenter-blocked sites |
These aren't in any default chain — opt in by passing proxy=X. Chain with proxy=X,scrapeowl to fall back on failure.
| Driver | Purpose | Example |
|---|---|---|
| browser | Cloudflare Browser Rendering API (managed, paid) | proxy=browser |
| browser_fetch | Generic CF Puppeteer worker — renders any JS-heavy URL, returns HTML. ~25s cold start, use timeout=45. | proxy=browser_fetch,scrapeowl |
| scrapedo | Scrape.do API (test-only). Supports render_js (headless browser), premium (residential/mobile/VIP), country, wait_for. Not in any default chain. | proxy=scrapedo&render_js=1&premium=1 |
| scamadviser_cf | Site-specific CF Puppeteer worker for scamadviser.com check-website pages. | proxy=scamadviser_cf,scrapeowl |
| trustpilot_cf | Site-specific CF Puppeteer worker for trustpilot.com review pages. | proxy=trustpilot_cf,scrapeowl |
| none | Direct fetch from our server (no proxy). Use for APIs where your server IP is whitelisted. | proxy=none |
Default TTL: 7 days. Cache key = URL + method + POST body.
| Priority | Source | Description |
|---|---|---|
| 1 | cache=N | Per-request override (days) |
| 2 | Cache rules | Admin URL patterns (e.g. %facebook.com% = 30d) |
| 3 | Global default | 7 days |
| Limit | Default | Scope |
|---|---|---|
| Customer/hour | 1,000 | Per API key |
| Caller IP/hour | 2,000 | Per server IP |
| Originator IP/hour | 100 | Per end-user IP |
| Customer/day | Unlimited | Daily cap |
curl -s 'https://www.botanon.com/api/fetch?url=https://example.com&apikey=YOUR_KEY&tag=test&ua=random' | jq .
PHP
$params = http_build_query(['url' => 'https://example.com', 'apikey' => 'YOUR_KEY', 'tag' => 'test', 'ua' => 'random']);
$data = json_decode(file_get_contents("https://www.botanon.com/api/fetch?{$params}"), true);
echo $data['body'];
Python
import requests
resp = requests.get("https://www.botanon.com/api/fetch", params={
"url": "https://example.com", "apikey": "YOUR_KEY", "tag": "test", "ua": "random",
}, timeout=35)
data = resp.json()
print(data["body"])
Node.js
const params = new URLSearchParams({url: 'https://example.com', apikey: 'YOUR_KEY', tag: 'test', ua: 'random'});
const data = await (await fetch(`https://www.botanon.com/api/fetch?${params}`)).json();
console.log(data.body);
import json
resp = requests.post("https://www.botanon.com/api/fetch", json={
"url": "https://api.bing.microsoft.com/v7.0/search?q=test",
"apikey": "YOUR_KEY",
"headers": {"Ocp-Apim-Subscription-Key": "YOUR_BING_KEY"},
"proxy": "none",
"tag": "bing",
})
results = json.loads(resp.json()["body"])
curl -s 'https://www.botanon.com/api/fetch?url=https://spa-app.com&apikey=YOUR_KEY&proxy=scrapeowl&render_js=1&premium=1&country=US&cache=30' | jq .
curl -s -X POST https://www.botanon.com/api/fetch \
-H 'Content-Type: application/json' \
-d '{"url":"https://api.example.com/search","apikey":"YOUR_KEY","method":"POST","post_data":"{\"q\":\"test\"}","post_type":"json","tag":"search"}' | jq .
curl -s --compressed 'https://www.botanon.com/api/fetch?url=https://example.com&apikey=YOUR_KEY&compress=1&cache=30' | jq .
import requests, time
def fetch(url, tag="agent", retries=3):
for i in range(retries):
resp = requests.get("https://www.botanon.com/api/fetch", params={
"url": url, "apikey": "YOUR_KEY", "tag": tag, "ua": "random",
"refresh": 1 if i > 0 else 0,
}, timeout=35)
data = resp.json()
if data["success"]:
return data
if data.get("error_code", "").startswith("RATE_LIMIT"):
time.sleep(data.get("retry_after", 60))
continue
if data.get("http_code") in [404, 401, 403]:
return data # don't retry client errors
time.sleep(2 ** i)
raise Exception(f"Failed after {retries} attempts")
Search Google Maps, look up places, and queue or instantly run crawls. All endpoints require an apikey.
Base path: /api/gmaps/
| Method | Path | Description |
|---|---|---|
| GET | /api/gmaps/search | Search Google Maps |
| GET | /api/gmaps/place/{id} | Look up scraped place |
| POST | /api/gmaps/crawl | Queue or instant crawl |
| GET | /api/gmaps/stats | Usage statistics |
/api/gmaps/search?apikey=KEY&query=QUERY| Param | Required | Default | Description |
|---|---|---|---|
| apikey | yes | — | API key |
| query | yes | — | Search string (e.g. "bars Derby") |
| depth | no | 1 | Pagination depth: 1≈20 results, 5≈40-50, 10+≈~120 |
| tier | no | auto | Force: gosom, residential, scrapeowl |
| refresh | no | 0 | 1 to bypass 30-day cache |
curl "https://www.botanon.com/api/gmaps/search?apikey=KEY&query=bars+Derby&depth=3"
Response
{
"query": "bars Derby",
"depth": 3,
"tier": "residential",
"cached": false,
"duration_seconds": 30.3,
"places": [
{
"place_id": "0x4879...",
"title": "Belong",
"category": "Bar",
"review_rating": 4.9,
"review_count": 329,
"phone": "+44 ...",
"website": "https://...",
"latitude": 52.922,
"longitude": -1.477,
"photos": ["https://..."],
"reviews": [{ "author": "...", "rating": 5, "text": "..." }]
}
]
}
/api/gmaps/place/{place_id}?apikey=KEYReturns a previously-scraped place by Google place_id. Use /search or /crawl first to populate.
# Place ID in URL path
curl "https://www.botanon.com/api/gmaps/place/0x2fb4e9b346dee95b:0xd5811e26f15ce74d?apikey=KEY"
# Or as query parameter
curl "https://www.botanon.com/api/gmaps/place?apikey=KEY&place_id=0x2fb4e..."
Response
{
"success": true,
"cached": true,
"place": {
"place_id": "0x2fb4e9b346dee95b:0xd5811e26f15ce74d",
"title": "Protego Pest Control",
"category": "Pest control service",
"phone": "+44 7897 314478",
"review_rating": 5.0,
"review_count": 42,
"photos": ["https://..."],
"reviews": [...]
}
}
/api/gmaps/crawl (JSON body)Two modes: queue (background processing, ~40 queries/hour) or instant (synchronous, returns results in 10-60s).
| Param | Required | Default | Description |
|---|---|---|---|
| apikey | yes | — | API key |
| query | yes* | — | Google Maps search string. *Not required if place_id is provided* |
| place_id | no | — | Google Place ID — auto-derives query from place's location + category |
| mode | no | queue | queue or instant |
| priority | no | 5 | 1-9 (1=highest). Queue mode only |
| location | no | auto | Location label (e.g. "Sevenoaks, Kent") |
| region | no | — | Region label (e.g. "Kent") |
| category | no | auto | Category (e.g. "pest_control") |
| depth | no | 5 | Pagination depth (1-20) |
| tier | no | auto | Force tier |
| refresh | no | 0 | Bypass cache (instant mode only) |
curl -X POST https://www.botanon.com/api/gmaps/crawl \
-H 'Content-Type: application/json' \
-d '{
"apikey": "YOUR_KEY",
"query": "pest_control in Sevenoaks, Kent, GB",
"mode": "queue",
"priority": 1,
"region": "Kent",
"category": "pest_control"
}'
Queue response (201)
{
"success": true,
"action": "queued",
"queue_id": 35361,
"query": "pest_control in Sevenoaks, Kent, GB",
"priority": 1,
"message": "Queued at priority 1. Crawler processes ~40 queries/hour."
}
Instant mode example
curl -X POST https://www.botanon.com/api/gmaps/crawl \
-H 'Content-Type: application/json' \
-d '{
"apikey": "YOUR_KEY",
"query": "restaurants in Albufeira, PT",
"mode": "instant",
"depth": 3
}'
Instant response (200)
{
"success": true,
"action": "instant",
"query": "restaurants in Albufeira, PT",
"tier": "residential",
"duration_seconds": 28.5,
"places_count": 12,
"places": [ ... ]
}
curl -X POST https://www.botanon.com/api/gmaps/crawl \
-H 'Content-Type: application/json' \
-d '{
"apikey": "YOUR_KEY",
"place_id": "ChIJqfzVUcwEdkgR7cvsVYJ8daE"
}'
place_id response (201) — note the place_context
{
"success": true,
"action": "queued",
"queue_id": 35401,
"query": "Pub in London, GB",
"priority": 5,
"location": "London",
"category": "Pub",
"place_context": {
"source_place_id": "ChIJqfzVUcwEdkgR7cvsVYJ8daE",
"source_title": "The Lamb & Flag, Covent Garden",
"derived_location": "London",
"derived_category": "Pub"
}
}
place_id with category override — find restaurants near a pub
curl -X POST https://www.botanon.com/api/gmaps/crawl \
-H 'Content-Type: application/json' \
-d '{
"apikey": "YOUR_KEY",
"place_id": "ChIJqfzVUcwEdkgR7cvsVYJ8daE",
"category": "restaurant",
"priority": 2
}'
# → Derives: "restaurant in London, GB"
category param.
/api/gmaps/stats?apikey=KEYReturns your Google Maps API usage statistics.
Search Twitter/X using authenticated accounts with cookie-based auth. Results are cached for 24 hours.
| Method | Path | Description |
|---|---|---|
| POST | /api/twitter-search | All actions via JSON body |
/api/twitter-search (JSON body)| Param | Required | Default | Description |
|---|---|---|---|
| apikey | yes | — | API key |
| action | no | search | search, add-account, or accounts |
| q | yes* | — | Search query (supports Twitter operators: OR, from:, since:, -filter:) |
| max | no | 20 | Max results (capped at 50) |
| no_cache | no | false | true to bypass 24h cache |
curl -X POST https://www.botanon.com/api/twitter-search \
-H 'Content-Type: application/json' \
-d '{
"apikey": "YOUR_KEY",
"q": "faro airport queue OR delay OR busy -filter:retweets",
"max": 10
}'
Response
{
"success": true,
"query": "faro airport queue OR delay OR busy",
"cached": true,
"cache_age_minutes": 45,
"tweet_count": 15,
"tweets": [
{
"id": "1234567890123456789",
"text": "Massive queue at Faro airport...",
"author": "traveller_uk",
"author_name": "UK Traveller",
"created_at": "2026-05-06T14:30:00Z",
"retweet_count": 5,
"like_count": 12,
"url": "https://x.com/traveller_uk/status/1234567890123456789"
}
]
}
/api/twitter-search (action: "tweet")Post as a stored posting account (account) or with an inline cookie string (cookies, must contain auth_token and ct0; not stored). Optional: images (up to 4; URLs, data: URIs or base64; jpeg/png/webp ≤5 MB, or one GIF ≤15 MB), reply_to (tweet ID), proxy. Links in the text count as 23 characters. X blocks datacenter IPs, so use a residential proxy.
curl -X POST https://www.botanon.com/api/twitter-search \
-H 'Content-Type: application/json' \
-d '{
"apikey": "YOUR_KEY",
"action": "tweet",
"account": "mybrand",
"text": "New menu is live https://example.com/menu",
"images": ["https://example.com/menu.jpg"]
}'
{
"success": true,
"tweet_id": "1839012345678901234",
"url": "https://x.com/mybrand/status/1839012345678901234"
}
Cookies are obtained from Chrome DevTools (Application → Cookies → x.com). They last ~1-2 months.
Add accountcurl -X POST https://www.botanon.com/api/twitter-search \
-H 'Content-Type: application/json' \
-d '{
"apikey": "YOUR_KEY",
"action": "add-account",
"username": "myxaccount",
"auth_token": "abc123...",
"ct0": "xyz789..."
}'
Add posting account (full cookie string)
curl -X POST https://www.botanon.com/api/twitter-search \
-H 'Content-Type: application/json' \
-d '{
"apikey": "YOUR_KEY",
"action": "add-account",
"purpose": "post",
"username": "mybrand",
"cookies": "auth_token=abc...; ct0=xyz...; twid=..."
}'
Posting accounts stay out of the search rotation and are only usable with the API key that added them.
List accountscurl -X POST https://www.botanon.com/api/twitter-search \
-H 'Content-Type: application/json' \
-d '{"apikey": "YOUR_KEY", "action": "accounts"}'
no_cache sparingly.