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 Captcha Pages

Symptom: The API returns HTML containing a captcha challenge 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.

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 will always cause a timeout.

429 Too Many Requests (RPM Limit)

Symptom: API returns 429 status with a "rate limit exceeded" message.

Cause: You've exceeded your requests-per-minute (RPM) limit. This is different from concurrency limits (see 503 below).

Solution: Use the retryAfter field from the response to wait the right amount of time before retrying:

import time
import requests

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:
            body = resp.json()
            wait = body.get("retryAfter", 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"}
)

Check your current usage in the Dashboard to see your rate limits.

503 Service Unavailable

Symptom: API returns 503 status.

Cause: This happens in two cases:

  1. Concurrency limit hit. You have too many simultaneous requests running. This is different from 429, which limits requests per minute. With 503, you haven't exceeded your RPM, but you've maxed out the number of requests that can run at the same time.
  2. Service temporarily disabled. A maintenance window is in progress.

Both cases include a retryAfter field in the response.

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):
            body = resp.json()
            wait = body.get("retryAfter", 2 ** i)
            time.sleep(wait)
            continue
        return resp
    raise Exception("Request not resolved after retries")

If you're hitting 503 concurrency limits regularly, reduce the number of parallel requests in your scraping pipeline, or check your plan's concurrency limit in the Dashboard.

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.

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/Reserved IP

Symptom: The API returns 400 with Target <ip> resolves to a private/reserved IP 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": "Target <ip> resolves to a private/reserved IP" }

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. The body comes back as a base64 buffer 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, base64-decode, then apply 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: Anti-Bot Defenses.

Add a validate.data.accept substring that only the real page carries. Without it, a challenge page 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: August 12, 2026