API 错误
如何处理来自 FourA API 的错误。
错误响应格式
API 对所有错误均返回扁平的 JSON 对象。没有嵌套的 error 对象或错误代码。
{
"error": "Invalid API key"
}
某些错误在顶层包含额外字段,例如 status、service、retryAfter、current 或 limits:
{
"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"
}
proxy 和 ignoreProxies 字段有它们自己的 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 字段。并发超限形式还包含 current 和 limits。
{
"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,请勿基于传输状态进行分支处理。请改为从响应体中读取 status 和 error。来自 /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-1251、gbk、shift_jis、iso-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")
相关文档
- Rate Limits:并发与 RPM 详情
- 请求结果:七种结果值说明
- 常见问题:表现、原因与修复方法
- 反爬虫防御:当 body 为质询页面而非错误时