API 오류

FourA API 오류 처리 방법입니다.

오류 응답 형식

API는 모든 오류에 대해 플랫 JSON 객체를 반환합니다. 중첩된 error 객체는 없습니다. 오류에 기계 판독 가능한 코드가 있는 경우 최상위 필드로 제공됩니다. 플랜 한도 초과 시 reason, 사용 가능한 출구가 없는 프록시 호출 시 code 필드가 포함됩니다.

{
  "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 }
}

Request 추적

FourA가 전혀 읽을 수 없는 본문(잘못된 형식의 JSON 또는 100 KB 초과 본문)을 제외한 모든 API response(성공 또는 오류)에는 해당 호출의 UUID가 포함된 X-FourA-Request-Id header가 포함됩니다. 이러한 요청은 ID가 할당되기 전에 거부됩니다. 이 ID를 직접 기록해 두십시오. 특정 request에 발생한 문제에 관해 지원팀에 문의해야 하는 경우, 이 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 본문에 필수 필드가 누락되었거나, 잘못된 값이 포함되어 있거나, API가 조회를 거부하는 대상을 지정했습니다.

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

동일한 400 에러는 SSRF 보호에도 적용됩니다. url이(가) 사설, 루프백 또는 기타 예약된 IP 대역(RFC 5735, RFC 6598, IPv6 예약 블록)으로 확인되는 경우, 해당 request는 FourA 네트워크를 벗어나기 전에 거부됩니다.

{
  "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."
}

<target>은(는) 주소이거나 호스트 이름 및 확인된 주소입니다. 파싱되지 않거나 http:// 또는 https://이(가) 아닌 URL도 동일하게 400을 반환합니다.

조회할 수 없는 호스트 이름은 거부되지 않습니다. 호출은 FourA가 도달할 수 없는 다른 대상과 마찬가지로 status: 0 및 이유(could not resolve <host>: <reason>)와 함께 HTTP 200으로 반환되며 요금이 청구되지 않습니다.

본문의 잘못된 형식의 JSON은 필드를 읽기 전에 동일한 방식으로 거부됩니다.

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

proxy 및 ignoreProxies 필드에는 고유한 400 에러가 있습니다. 두 필드 모두 이전 response에서 반환된 불투명 proxy ID를 사용하므로, 다른 값을 전달하면 디코딩에 실패합니다.

Message What happened
Invalid proxy format proxy 값이 FourA에서 발급한 proxy ID가 아닙니다. 원시 proxy 주소를 전달하면 여기에 해당합니다.
Invalid ignoreProxies format ignoreProxies 의 항목 중 하나가 proxy ID가 아닙니다.
Proxy not found ID 디코딩은 정상 처리되었으나 활성 exit로 더 이상 확인되지 않습니다. 새 ID를 선택하십시오.
Managed exit: this proxy id cannot be pinned to a request 유효한 exit이지만, FourA가 지정된 request를 위해 유지할 수 있는 대상이 아닙니다. 요금제에 사용 가능한 프리미엄 트래픽이 남아 있지 않을 때 프리미엄 exit ID를 사용하면 여기에 해당합니다. 반환되었던 기존 세션을 재사용하거나, POST /api/proxy/ 를 통해 호출하여 임의로 선택되는 exit를 사용하십시오.

해결 방법: request에 모든 필수 필드가 포함되어 있는지, URL이 http:// 또는 https:// 을 사용하는지, 호스트가 공용 주소로 확인되는지, 그리고 모든 proxy 값이 이전 response에서 그대로 복사한 ID인지 확인하십시오.

이들은 client_error 결과입니다. request가 FourA 외부로 전송되지 않았으므로 크레딧이 차감되지 않았습니다.

401: Unauthorized

API key가 누락되었거나 유효하지 않습니다.

누락된 key:

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

유효하지 않은 키:

{
  "error": "Invalid API key"
}

해결 방법: X-API-Key header에 유효한 키가 포함되어 있는지 확인하세요. 필요한 경우 대시보드에서 새 키를 생성할 수 있습니다.

403: Not in Your Plan

요청한 endpoint 또는 파라미터가 현재 플랜에 포함되어 있지 않습니다. response는 X-FourA-Limit를 설정하고 body의 reason에 동일한 코드를 반환합니다.

{
  "error": "The browser endpoint is not included in your plan (Free). Upgrade to use it.",
  "reason": "plan_limit_feature",
  "documentation": "https://foura.ai/prices"
}

플랜에서 제외된 endpoint이거나 지오 타게팅이 없는 플랜에서 exitCountries를 사용한 경우 reason는 plan_limit_feature이며, 프리미엄 출구가 없는 플랜에서 exitClass: premium를 사용한 경우 plan_limit_premium입니다. error 문자열은 해당 endpoint 또는 매개변수를 지정합니다.

FourA에서 반환하는 403은 대상 사이트와 무관합니다. 대상에는 연결조차 시도하지 않았습니다. 대상 사이트가 반환한 403은 본문 내에 status: 403가 포함된 HTTP 200으로 수신됩니다.

해결 방법: 매개변수를 제거하거나, 플랜에 포함된 endpoint를 호출하거나, 플랜을 업그레이드하세요. 대기해도 결과가 달라지지 않으므로 Retry-After는 설정되지 않습니다. 아무것도 소모되지 않았습니다. 결과는 rate_limit이며, success만 청구됩니다.

413: Payload Too Large

JSON request body가 FourA 허용 한도(100 KB)를 초과합니다. 본문을 읽기 전에 거부되므로 응답은 JSON이 아니며 X-FourA-Request-Id도 포함되지 않습니다.

해결 방법: 더 작은 data 페이로드를 전송하세요. 아무것도 소모되지 않았습니다.

429: Rate Limited

두 가지 서로 다른 검사에서 429로 응답하며, 포함되는 필드도 서로 다릅니다.

사용 중인 플랜의 자체 한도. 응답에는 호출을 거부한 한도를 명시하는 X-FourA-Limit 헤더가 설정되고 본문의 reason 아래에 동일한 코드가 포함됩니다.

{
  "error": "Concurrency limit reached: your plan allows 50 simultaneous proxy request(s). Retry when an in-flight request finishes.",
  "reason": "plan_limit_concurrency",
  "documentation": "https://foura.ai/prices",
  "limit": 50,
  "in_flight": 51,
  "retry_after_seconds": 1
}

reason의 값은 plan_limit_concurrency, plan_limit_rate, plan_limit_browser_daily, plan_limit_credits, plan_limit_bandwidth 중 하나입니다. 대기가 필요한 경우 대기 시간은 retry_after_seconds 및 Retry-After 헤더에 포함되며, retryAfter에는 포함되지 않습니다. 할당량은 초 단위가 아니라 UTC 자정에 재설정되므로 plan_limit_browser_daily에는 둘 다 포함되지 않습니다. 차감된 비용은 없습니다. 결과는 rate_limit이며, success에 대해서만 요금이 청구됩니다.

플랫폼의 공유 할당량. X-FourA-Limit 헤더가 없으며, 대기 시간은 retryAfter에 있습니다.

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

current 및 limits은(는) 개별 계정이 아닌 전체 트래픽에 대한 서비스 상태를 나타냅니다. 여기서 거부된 경우 FourA 시스템이 사용 중임을 의미합니다.

해결 방법: response에 포함된 Retry-After, retry_after_seconds, retryAfter 중 해당하는 시간 동안 대기하십시오. 동시성 또는 rate limit이 발생한 경우 거부된 배치를 다시 전송하지 말고 열려 있는 request 수를 제한하십시오. 일일 또는 청구 주기 한도에 도달한 경우 실행을 중단하십시오. 각 필드에 대한 자세한 내용은 Rate Limits를 참조하고, 구현 패턴은 Run Requests in Parallel를 확인하십시오.

500: Server Error

서버 내부 오류가 발생했습니다.

해결 방법: 잠시 후 request를 재시도하십시오. 오류가 지속되면 상태 페이지를 확인하거나 실패한 response의 X-FourA-Request-Id와(과) 함께 지원팀에 문의하십시오.

502: Upstream Unavailable

FourA가 자체 엔진에 도달했으나 응답을 사용할 수 없습니다.

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

해결 방법: 짧은 백오프(backoff) 후 다시 시도하십시오. 서버 측 문제이므로 비용이 청구되지 않습니다. 결과는 service_error이며 success에 대해서만 비용이 청구됩니다.

504: Upstream Timeout

엔진이 이 request의 제한 시간 내에 완료되지 않았습니다.

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

504 오류는 키, 파라미터, proxy 문제가 아닌 작업 소요 시간 때문에 발생합니다. 느린 대상 서버, 초기 챌린지 해결 지연, 대용량 페이지가 주된 원인입니다.

해결 방법: request의 timeout_ms 값을 늘리거나(Single 최대 120000, Browser 최대 120000, Auto 최대 180000) 재시도하세요. FourA는 설정된 시간 한도에 약간의 여유 시간을 더해 대기하므로, 시간을 더 늘리면 실제로 처리 시간이 확보됩니다.

503: Service Disabled or At Capacity

503 오류는 유지보수로 인해 일시적으로 서비스를 사용할 수 없거나 플랫폼의 동시성 한도가 초과되었음을 의미합니다. 두 경우 모두 동일한 키(error, status, service, retryAfter, current, limits)를 반환합니다. 필드의 존재 여부가 아닌 error 문자열로 두 경우를 구분하세요.

{
  "error": "Service disabled",
  "status": 503,
  "service": "single",
  "retryAfter": 60,
  "current": { "concurrency": 0, "rpm": 0 },
  "limits": { "maxConcurrency": 500, "maxRpm": 3000 }
}

Service disabled는 유지보수 상태이며, 측정이 시작되기 전에 request가 거부되었으므로 두 카운터 모두 current 값이 0로 표시됩니다. Service at capacity는 동시성 형태이며, 이 경우 current는 플랫폼의 실제 사용량을 나타냅니다. 해당 형태는 Rate Limits를 참조하십시오.

해결 방법: retryAfter초 동안 기다린 후 다시 시도하십시오. 상태 페이지에서 진행 중인 유지보수 일정을 확인할 수 있습니다.

세 번째 503 형태에는 retryAfter가 없습니다. 이는 호출이 도달했을 때 endpoint 뒤의 엔진이 재시작 중이었음을 의미합니다.

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

1~2초 후 다시 시도하십시오.

/api/auto/에서 실패 결과 확인하기

POST /api/auto/은(는) 모든 단계(rung)가 실패하더라도 ladder가 실행된 경우 항상 HTTP 200으로 응답합니다. 실제 결과는 본문(body)에 포함되어 있습니다.

{
  "status": 403,
  "error": "exit blocked by the target defense",
  "attempts": 7,
  "meta": { "rung": "fail", "solved": false, "attempts": 7, "credits": 47 }
}

status는 대상이 응답한 마지막 상태 코드이거나, 시도가 대상에 도달하지 못한 경우의 502입니다 (시간 예산이 먼저 소진된 경우 504). Auto가 수락할 수 없는 request 필드(예: 5000 미만 또는 180000 초과의 timeout_ms)도 동일한 방식으로 반환됩니다. 시도가 이루어지기 전에 비용 청구 없이 "status": 400 및 error의 사유와 함께 HTTP 200으로 반환됩니다.

따라서 Auto 사용 시 전송 계층의 상태 코드로 분기하지 마십시오. 대신 body의 status 및 error를 확인하십시오. /api/auto/의 실제 non-200 응답은 FourA가 ladder 시작 전에 호출을 거부했거나 완료할 수 없었음을 의미합니다: 400 (잘못된 형식의 JSON, 또는 비공개/예약된 대상), 401, 413, 502, 503 또는 504. 사용자 또는 플랫폼의 제한 사항은 body 내에 해당 상태와 함께 200 응답 내부로 반환됩니다.

어떤 사이트에서 여러 번 연속으로 Auto 호출이 실패하면 Auto는 시도하지 않고 당분간 즉시 응답합니다: "error": "target temporarily unservable, retry later", "status": 503 및 초 단위의 retryAfter. 비용은 들지 않으며, retryAfter초 동안 대기하십시오.

하위 호출 중 하나에서 플랜 제한에 도달한 경우에도 HTTP 200으로 반환됩니다. body는 거부 내용 자체이며 reason, status 및 meta가 포함됩니다. response에는 직접 거부된 경우와 동일한 X-FourA-Limit header가 포함됩니다:

{
  "status": 429,
  "error": "Credit limit reached: 75,000 of 75,000 credits used this billing period. Upgrade your plan or wait for the reset.",
  "reason": "plan_limit_credits",
  "documentation": "https://foura.ai/prices",
  "used": 75000,
  "hard_stop": 75000,
  "retry_after_seconds": 86400,
  "resets_at": "2026-10-01T00:00:00.000Z",
  "meta": { "rung": "fail", "solved": false, "attempts": 1, "credits": 0 }
}

어떤 제한이 사다리 전체를 멈추고 어떤 제한이 특정 단계만 닫는지는 Smart Fetch (Auto)에서 다룹니다.

200 OK 내부의 타깃 측 실패

모든 실패가 2xx가 아닌 HTTP 상태로 나타나는 것은 아닙니다. 타깃이 HTTP 200으로 응답하지만 FourA의 응답에 error이 포함되어 있거나(예: 사용자의 validate 규칙이 본문을 거부함) 본문이 FourA에서 인식한 확인 페이지인 경우 결과는 application_error입니다. 타깃이 사용자의 validate 규칙에서 허락하지 않는 2xx 외의 상태를 반환하면 결과는 application_fail이 되며 본문은 변경 없이 그대로 전달됩니다.

두 경우 모두 요금이 청구되지 않으며 success만 청구됩니다. 또한 FourA의 모든 브라우저가 사용 중일 때 Browser는 HTTP 200과 함께 "error": "No available browser slot"을 반환할 수 있습니다. 이는 요금이 청구되지 않으며 몇 초 후에 다시 시도하십시오. 전체 분류 체계는 Outcomes 레퍼런스에서 다룹니다.

고정한 proxy를 통한 Single 호출에서도 본문과 함께 HTTP 200 및 "error": "The exit gave the same answer for <n> different sites"이 반환될 수 있습니다. 이는 FourA가 해당 exit에서 관련 없는 사이트들에 동일한 페이지를 반환하는 것을 감지했기 때문이며, 즉 해당 페이지는 요청한 페이지가 아닌 exit 자체의 페이지입니다. 이는 application_error이며 요금이 청구되지 않습니다. 이러한 exit를 자동으로 건너뛰는 POST /api/proxy/에서 새로운 exit를 가져오십시오.

Response 인코딩

FourA는 response 본문을 UTF-8로 자동 디코딩합니다. 타깃이 windows-1251, gbk, shift_jis, iso-8859-* 또는 Content-Type header나 HTML <meta charset> 태그에 선언된 기타 charset을 제공하는 경우, data(Single, proxy) 또는 body(Browser) 필드에서 변환된 UTF-8 문자열을 수신합니다.

바이너리 페이로드(이미지, protobuf, 원시 오디오)의 경우 request에 returnBuffer: true를 설정하십시오. 그러면 Single 및 Proxy는 charset 트랜스코딩 없이 원시 바이트를 포함하는 객체인 data({"type": "Buffer", "data": [<byte values>]})를 반환합니다.

재시도 전략

실용적인 재시도 정책:

import time
import requests

# Plan limits that no short wait will clear: stop the run instead of retrying.
STOP_ON = {
    "plan_limit_feature",
    "plan_limit_premium",
    "plan_limit_browser_daily",
    "plan_limit_credits",
    "plan_limit_bandwidth",
}

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 {}
        request_id = resp.headers.get("X-FourA-Request-Id", "?")

        limit = resp.headers.get("X-FourA-Limit")
        if limit in STOP_ON:
            raise RuntimeError(f"{limit} (request {request_id}): {body.get('error')}")

        # Plan limits send Retry-After and retry_after_seconds; platform limits send retryAfter.
        header = resp.headers.get("Retry-After")
        retry_after = (
            int(header) if header and header.isdigit()
            else body.get("retry_after_seconds") or body.get("retryAfter") or 2 ** attempt
        )

        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/403/404 won't fix themselves
        raise RuntimeError(f"{resp.status_code} (request {request_id}): {body.get('error')}")

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

Proxy 실패 시 리포트 포함

시도 횟수를 모두 소진한 POST /api/proxy/ 호출은 HTTP 오류 코드가 아닌 오류 envelope이 포함된 HTTP 200으로 반환됩니다. 오류 문자열은 짧고 항상 동일한 형태이므로, 개수를 포함한 attemptReport 객체가 함께 전달됩니다:

{
  "error": "Download maxTry limit reached",
  "attemptReport": {
    "total": 25,
    "noResponse": 0,
    "defense": 0,
    "contentRejected": 25,
    "statusRejected": 0,
    "other": 0,
    "vendors": [],
    "profilesTried": ["default"],
    "summary": "25 attempt(s): 25 returned HTTP 200 with no defense present and were rejected only by your validate.data - the page was fetched, your content rule did not match it"
  },
  "total": 34.812
}

에러 옆에 attemptReport.summary를 기록하면 출구 노드가 차단되었는지, 비활성 상태인지, 또는 자체 validate 규칙에서 거부된 페이지를 반환했는지 바로 파악할 수 있습니다. 각 카운트에 대한 대처 방법 및 필드 레퍼런스: 프록시 요청 재시도 횟수 초과 원인.

관련 항목

최근 업데이트: 2026년 9월 30일