API 错误

如何处理来自 FourA API 的错误。

错误响应格式

API 对所有错误均返回扁平的 JSON 对象。没有嵌套的 error 对象或错误代码。

{
  "error": "Invalid API key"
}

某些错误在顶层包含额外字段,例如 statusserviceretryAftercurrentlimits

{
  "error": "Rate limit exceeded",
  "status": 429,
  "service": "single",
  "retryAfter": 5,
  "current": { "concurrency": 12, "rpm": 3000 },
  "limits": { "maxConcurrency": 500, "maxRpm": 3000 }
}

追踪请求

每个 API 响应(成功或错误)都包含一个带有该次调用 UUID 的 X-FourA-Request-Id header。请在您的系统中进行记录。如果您需要向支持团队查询特定请求的详情,该 ID 将帮助我们找到它。

curl -i -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://example.com"}'
# HTTP/1.1 200 OK
# X-FourA-Request-Id: 9f1c4e6c-7b2a-4d3e-8a1f-2c9d8e4a3b15
# Content-Type: application/json
# ...

错误类型

400: Bad Request

request body 缺少必填字段、包含无效值,或指定了 API 拒绝获取的目标。

{
  "error": "Invalid request body format"
}

相同的 400 错误也涵盖 SSRF 保护。如果您的 url 解析为私有、回环或其他保留 IP 范围(RFC 5735、RFC 6598、IPv6 保留块),该 request 将在离开 FourA 网络之前被拒绝:

{
  "error": "Target <ip> resolves to a private/reserved IP"
}

在读取任何字段之前,body 中格式错误的 JSON 将以相同方式被拒绝:

{
  "error": "Invalid JSON in request body"
}

proxyignoreProxies 字段有它们自己的 400 错误。两者都接受早期响应返回的不透明 proxy ID,因此任何其他内容都会解码失败:

消息 发生的情况
Invalid proxy format proxy 值不是 FourA 签发的 proxy ID。原始 proxy 地址会触发此错误。
Invalid ignoreProxies format ignoreProxies 中的某个条目不是 proxy ID。
Proxy not found ID 解码成功但不再解析为活跃的出口。请选择一个新的 ID。

**修复:**检查您的 request 是否包含所有必填字段,URL 是否使用 http://https://,主机是否解析为公共地址,以及任何 proxy 值是否是从早期 response 中原样复制的 ID。

401: 未授权

您的 API 密钥缺失或无效。

缺失密钥:

{
  "error": "Missing API key. Include X-API-Key header."
}

无效的密钥:

{
  "error": "Invalid API key"
}

**修复:**请验证您的 X-API-Key header 是否包含有效密钥。如有需要,请从 仪表板 生成新密钥。

429: Rate Limited

您在短时间内发送了过多 request。

{
  "error": "Rate limit exceeded",
  "status": 429,
  "service": "single",
  "retryAfter": 5,
  "current": { "concurrency": 12, "rpm": 3000 },
  "limits": { "maxConcurrency": 500, "maxRpm": 3000 }
}

**修复方法:**在发送更多 request 之前,请等待 retryAfter 中指定的秒数。有关详细信息,请参阅 rate limit

500: 服务器错误

服务端发生错误。

**修复方法:**稍后重试 request。如果错误仍然存在,请查看 状态页面,或提供失败 response 中的 X-FourA-Request-Id 以联系技术支持。

502: 上游不可用

FourA 已到达自身引擎,但无法使用返回结果。

{
  "error": "Upstream unavailable",
  "details": "..."
}

**修复:**使用短时间退避重试。这是我们端的问题,因此不收取任何费用:结果为 service_error,且仅对 success 计费。

504:上游超时

引擎未在此请求的时间预算内完成。

{
  "error": "Upstream timeout",
  "details": "the backend did not finish inside the time budget for this request"
}

504 错误与任务耗时有关,与您的密钥、参数或 proxy 无关。目标响应慢、冷启动 challenge 解决以及大页面是常见原因。

修复方法: 增加请求中的 timeout_ms (Single 最高支持 120000,Browser 最高支持 120000,Auto 最高支持 180000),或重试。FourA 会等待您声明的时间预算加上微小的裕量,因此请求更多时间确实能获得更多处理时间。

503: 服务已禁用或达到容量上限

503 表示服务因维护暂时不可用,或者您已达到并发限制。这两种响应均包含 retryAfter 字段。并发超限形式还包含 currentlimits

{
  "error": "Service disabled",
  "status": 503,
  "retryAfter": 60
}

**修复:**等待 retryAfter 秒后重试。状态页面列出了当前的维护窗口。

第三种 503 形式没有 retryAfter。这意味着当您的请求到达时,您的 endpoint 后端引擎正在重启:

{
  "error": "Backend service unavailable",
  "backend_status": 503
}

一两秒后重试。

/api/auto/ 读取失败信息

POST /api/auto/ 只要 ladder 运行就会返回 HTTP 200,即使所有 rung 都失败也是如此。实际结果包含在 body 中:

{
  "status": 0,
  "error": "all attempts failed",
  "attempts": 7,
  "meta": { "rung": "fail", "solved": false, "attempts": 7, "credits": 47 }
}

因此,对于 Auto,请勿基于传输状态进行分支处理。请改为从响应体中读取 statuserror。来自 /api/auto/ 的真正非 200 状态码意味着 FourA 在梯级机制启动前拒绝了调用:401、400、429 或 503,所有这些都在上文有记录。

200 OK 内部的目标端故障

并非每个故障都会显示为非 2xx HTTP 状态。当目标网站返回带有错误负载的 HTTP 200 时,FourA 仍会向您传递响应体,但会将该 request 分类为 application_error。当目标返回您的 validate 规则不接受的非 2xx 状态时,结果为 application_fail,且响应体会原样传回。

这两种情况都将计费,就像 request 在网络层级成功一样。Outcomes 参考文档涵盖了完整的分类。

响应编码

FourA 会自动将响应体解码为 UTF-8。如果目标提供 windows-1251gbkshift_jisiso-8859-*,或者在 Content-Type header 或 HTML <meta charset> 标签中声明的任何其他字符集,您将在 data(单个、proxy)或 body(浏览器)字段中收到纯洁的 UTF-8 字符串。

对于二进制负载(图像、protobuf、原始音频),请在 request 上设置 returnBuffer: true。响应体将作为 base64 缓冲区返回,且未应用任何字符集转码。

重试策略

实用的重试策略:

import time
import requests

def make_request(url, payload, api_key, max_retries=3):
    for attempt in range(max_retries):
        resp = requests.post(
            url,
            headers={"X-API-Key": api_key, "Content-Type": "application/json"},
            json=payload,
        )
        if resp.status_code == 200:
            return resp.json()

        body = resp.json() if resp.headers.get("content-type", "").startswith("application/json") else {}
        retry_after = body.get("retryAfter", 2 ** attempt)
        request_id = resp.headers.get("X-FourA-Request-Id", "?")

        if resp.status_code in (429, 503):
            time.sleep(retry_after)
            continue
        if resp.status_code >= 500:   # 500, 502, 503, 504 are all ours to fix
            time.sleep(2 ** attempt)
            continue

        # 400/401/404 won't fix themselves
        raise RuntimeError(f"{resp.status_code} (request {request_id}): {body.get('error')}")

    raise RuntimeError(f"Exhausted {max_retries} retries")

相关文档

更新于: 2026年8月12日