Common Issues

Solutions to the most common problems when using the FourA API.

Empty or Incomplete Content

Symptom: The API returns a 200 status but the data field is empty or missing expected content.

Cause: The target page uses JavaScript to render content after the initial page load.

Solution: Switch from the single endpoint to the browser endpoint. Use checkText to verify the content loaded:

curl -X POST https://eu.api.foura.ai/api/browser/ \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/products",
    "timeout_ms": 15000,
    "checkText": "product-list"
  }'

Note: the browser endpoint returns content in the body field (not data).

403 Forbidden or Verification Pages

Symptom: The API returns HTML containing a verification page or an access denied page.

Cause: The target site detected the request as automated and blocked it.

Solution: Use the proxy endpoint for automatic IP rotation:

curl -X POST https://eu.api.foura.ai/api/proxy/ \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "maxTries": 5,
    "request": {
      "method": "GET",
      "url": "https://example.com/prices",
      "unblocker": true
    }
  }'

If the issue persists, increase maxTries to give the proxy rotation more attempts.

A 403 that the target returned arrives as HTTP 200 with status: 403 inside the body. A 403 on the call itself, with an X-FourA-Limit header, is a different thing: see 403 Not in Your Plan.

Timeout Errors

Symptom: Requests fail with a timeout error.

Cause: The target page takes longer to load than the configured timeout.

Solution: Increase timeout_ms (default is 15s for single, 30s for browser, 45s for proxy):

curl -X POST https://eu.api.foura.ai/api/browser/ \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://slow-site.com",
    "timeout_ms": 60000
  }'

For browser requests, also verify your checkText value actually appears on the page. A typo fails the call with checkText:<your text> not found.

403 Not in Your Plan

Symptom: The API returns 403 with an X-FourA-Limit header and a reason of plan_limit_feature or plan_limit_premium.

{
  "error": "Geo targeting (the exitCountries parameter) is not included in your plan (Free). Remove the parameter or upgrade.",
  "reason": "plan_limit_feature",
  "documentation": "https://foura.ai/prices"
}

Cause: Your plan doesn't include the endpoint you called or the parameter you sent. plan_limit_feature covers an excluded endpoint and exitCountries without geo targeting; plan_limit_premium covers exitClass: premium without premium exits. The target was never contacted, and nothing was spent.

Solution: Remove the parameter, call an endpoint your plan includes, or upgrade. The Limits & Features tab of Usage & Limits lists what your plan includes. Don't retry unchanged: no Retry-After is set because waiting doesn't change the answer.

429 Too Many Requests

Symptom: API returns 429.

Cause: One of two checks refused the call, and the response tells you which. If it carries an X-FourA-Limit header, one of your plan's limits was reached: simultaneous requests or requests per minute on that endpoint, Browser requests for the day, or the credits or bandwidth for the billing period. If there's no such header, the platform's shared per-minute allowance for that service was full, which is about FourA's traffic and not yours.

Solution: Read X-FourA-Limit first. Wait when the limit is seconds away and stop when it isn't. Plan limits that a wait clears put the seconds in the Retry-After header and in retry_after_seconds; the shared limit puts them in retryAfter:

import time
import requests

# Plan limits that come back in hours or days, not seconds.
STOP_ON = {"plan_limit_browser_daily", "plan_limit_credits", "plan_limit_bandwidth"}

def make_request(endpoint_url, payload, retries=3):
    for i in range(retries):
        resp = requests.post(
            endpoint_url,
            headers={"X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json"},
            json=payload
        )
        if resp.status_code == 429:
            limit = resp.headers.get("X-FourA-Limit")
            if limit in STOP_ON:
                raise Exception(f"stopped by {limit}: {resp.json().get('error')}")
            header = resp.headers.get("Retry-After")
            body = resp.json()
            wait = (
                int(header) if header and header.isdigit()
                else body.get("retry_after_seconds") or body.get("retryAfter") or 2 ** i
            )
            time.sleep(wait)
            continue
        return resp
    raise Exception("Rate limit not resolved after retries")

# Example: single request
make_request(
    "https://eu.api.foura.ai/api/single/",
    {"method": "GET", "url": "https://example.com"}
)

If the header said plan_limit_concurrency or plan_limit_rate, the fix is to cap how many calls you keep open and how many you start per minute rather than to retry harder. Re-sending a refused batch immediately gets the whole batch refused again. Refused calls don't count toward your per-minute limit, but if they keep arriving at more than twice that limit, the refusals turn into a cooldown: the 429 body carries cooldown: true and asks you to pause for 30 seconds (retry_after_seconds: 30). Run Requests in Parallel has the pattern, and Usage & Limits in the Dashboard shows your live counters next to your caps.

503 Service Unavailable

Symptom: API returns 503 status.

Cause: This happens in two cases:

  1. The service is at capacity. FourA is already running as many requests on that engine as it's allowed to run at once, counted across all traffic rather than yours. Service at capacity in the error field. It usually clears in seconds.
  2. Service temporarily disabled. A maintenance window is in progress. Service disabled in the error field.

Both cases include a retryAfter field in the response. Neither one is a plan limit: your own plan's limits always answer with an X-FourA-Limit header on a 403 or a 429, never with 503.

Solution: Wait for retryAfter seconds, then retry:

import time
import requests

def make_request_with_retry(endpoint_url, payload, retries=3):
    for i in range(retries):
        resp = requests.post(
            endpoint_url,
            headers={"X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json"},
            json=payload
        )
        if resp.status_code in (429, 503):
            header = resp.headers.get("Retry-After")
            body = resp.json()
            wait = (
                int(header) if header and header.isdigit()
                else body.get("retry_after_seconds") or body.get("retryAfter") or 2 ** i
            )
            time.sleep(wait)
            continue
        return resp
    raise Exception("Request not resolved after retries")

A 503 at capacity is FourA being busy, so backing off and retrying is the whole answer. If you're being refused with 429 and X-FourA-Limit instead, that one is yours: reduce the number of parallel requests in your pipeline.

504 Upstream Timeout

Symptom: The API returns 504 with {"error": "Upstream timeout"}.

Cause: The work didn't finish inside the time budget you declared for the request. A slow target, a cold challenge solve, or a very large page will all do it. It isn't a problem with your key, your parameters, or your proxy.

Solution: Give the call more time, or retry. FourA waits for your timeout_ms plus a small margin, so raising it genuinely extends the wait:

{
  "url": "https://slow-site.com/report",
  "timeout_ms": 90000
}

For /api/auto/ on a protected target, a cold first call can take tens of seconds. Its timeout_ms covers the whole ladder and accepts up to 180000.

When /api/auto/ runs out of that budget itself, the call still answers HTTP 200. The body carries an error that starts with time budget exhausted, and status is usually 504 (an earlier failed attempt can leave its own status there instead). Raise timeout_ms or retry.

502 Upstream Unavailable

Symptom: The API returns 502 with {"error": "Upstream unavailable"}, or 503 with {"error": "Backend service unavailable"}.

Cause: FourA reached its own engine but couldn't use the reply, usually because an instance was restarting.

Solution: Retry with a short backoff. Both classify as service_error, and only success is billed, so a retry costs you nothing extra. If it lasts more than a minute or two, check the status page.

401 Authentication Errors

Symptom: Every request returns 401 Unauthorized.

Checklist:

  1. Verify the header is X-API-Key: YOUR_API_KEY (not Authorization: Bearer or Api-Key)
  2. Check for extra whitespace or newlines in your API key
  3. Create a fresh key from the Dashboard if the current one might be compromised

400 Target Resolves to a Private or Reserved IP

Symptom: The API returns 400 with Refusing to fetch <target>: target resolves to a private or reserved IP range before the request leaves FourA.

Cause: Your url resolves to a private, loopback, or reserved IP range (RFC 5735, RFC 6598, or IPv6 reserved blocks). FourA refuses these targets so its network can't be used to reach internal hosts.

Solution: Fetch a public URL. If you're testing, use a public target like https://example.com or https://httpbin.org/get. If your intended target is a service you run, expose it on a public hostname first.

{ "error": "Refusing to fetch <target>: target resolves to a private or reserved IP range. The FourA scraping API only forwards requests to public internet hosts." }

A host name that can't be looked up isn't refused. The call comes back as HTTP 200 with status: 0 and the reason (could not resolve <host>: <reason>), like any target FourA can't reach, and it isn't billed.

400 Target Is the FourA API Itself

Symptom: The API returns 400 with Refusing to fetch <url>: that is the FourA API itself, not a page. before the request leaves FourA.

Cause: Your url points at api.foura.ai or eu.api.foura.ai. A crawler usually does this when it resolves a relative link from a page against the API URL instead of the page's own URL, so /category/news/ turns into https://eu.api.foura.ai/category/news/. Nothing is fetched and no credits are spent.

Solution: Resolve every link against the URL of the page it came from before you send it, for example with urljoin(page_url, href) in Python. The Inspect endpoint (/inspect) is the one address on the API host you can fetch through FourA.

no_eligible_proxy When Using exitCountries

Symptom: A /api/proxy/ call with exitCountries returns HTTP 200 with a JSON error envelope:

{
  "error": "No eligible proxy found for exit countries: CZ, GB",
  "code": "no_eligible_proxy",
  "details": { "exitCountries": ["CZ", "GB"] },
  "total": 0.084
}

Cause: The current proxy pool has no working exit whose target-visible country matches your allowlist. FourA never falls back to an unrequested country when you set exitCountries.

Solution: Preserve the requested scope and retry later. The pool is refreshed roughly every ten minutes, so a country that has no match now often gains one within the hour.

import time, requests

def fetch_scoped(url, countries, max_attempts=6, wait_sec=600):
    for _ in range(max_attempts):
        r = requests.post("https://eu.api.foura.ai/api/proxy/",
            headers={"X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json"},
            json={"maxTries": 5, "exitCountries": countries,
                  "request": {"method": "GET", "url": url}}).json()
        if r.get("code") == "no_eligible_proxy":
            time.sleep(wait_sec)
            continue
        return r
    raise RuntimeError(f"no eligible exit in {countries} after {max_attempts} attempts")

Only widen the country list if your workflow's country requirement genuinely changed. Silent fallbacks to other countries can break geo-dependent logic downstream.

Response Body Comes Back as Garbled Text

Symptom: The response data (or body) contains mojibake or unreadable characters when the target uses a non-UTF-8 charset.

Cause: By default FourA auto-decodes response bodies to UTF-8 based on the target's Content-Type header or an HTML <meta charset> tag. If the target lies about its charset, you get garbled text.

Solution: For binary payloads (images, protobuf, raw audio), set returnBuffer: true on the request. Single and Proxy then return data as an object holding the raw bytes, {"type": "Buffer", "data": [<byte values>]}, with no charset transcoding applied.

{
  "method": "GET",
  "url": "https://example.com/image.png",
  "returnBuffer": true
}

For text targets that mis-declare their charset, decode the raw bytes yourself: fetch with returnBuffer: true, read the byte values in data.data, then decode them with the correct charset.

Unexpected HTML Instead of JSON

Symptom: You expected JSON from the target site but received HTML.

Cause: The target page may serve different content based on headers.

Solution: Add an Accept header and enable unblocker for realistic browser headers:

curl -X POST https://eu.api.foura.ai/api/single/ \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "GET",
    "url": "https://api.example.com/data",
    "headers": [["Accept", "application/json"]],
    "unblocker": true
  }'

You can also set tryJsonData to true to have FourA automatically parse JSON responses.

The Body Is a Challenge Page, Not Content

Symptom: The call succeeded, status is 200, but data (or body) is a bot check rather than the page you wanted.

Cause: The target ran a bot check that FourA met but couldn't clear. The response says so: Single and Proxy return defense with solved: false, and Browser returns defenseSolved: false with the vendor in defenses.present.

Solution: Check defense.vendor first, then escalate. Try a different browser profile on Single, move up to Proxy for a different exit, or use Browser so JavaScript runs. Full field reference and vendor list: Site checks.

Add a validate.data.accept substring that only the real page carries. A check page FourA recognises is never a success: it comes back with an X-FourA-Check-Page header and isn't billed. Without validate, a check page FourA doesn't recognise, returned with HTTP 200, counts as a success, and you find out downstream instead of at the call.

Still Stuck?

If none of the above solutions work:

  1. Check the status page for any ongoing incidents
  2. Review your request metrics in the Dashboard
  3. Contact support at support@foura.ai with your request details (include the X-FourA-Request-Id from the failed response)

Next Steps

Last updated: October 7, 2026