API 오류

FourA API에서 발생하는 오류를 처리하는 방법입니다.

오류 응답 형식

API는 모든 오류에 대해 플랫(flat)한 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 }
}

Request 추적

모든 API response(성공 또는 오류)에는 해당 호출에 대한 UUID가 포함된 X-Foura-Request-Id header가 포함되어 있습니다. 이를 귀사의 로그에 기록해 두십시오. 특정 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 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"
}

해결 방법: request에 모든 필수 필드가 포함되어 있는지, URL에 http:// 또는 https://를 사용하는지, 호스트가 공인 IP 주소로 확인되는지 확인하십시오.

401: Unauthorized

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

Key 누락:

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

유효하지 않은 Key:

{
  "error": "Invalid API key"
}

해결 방법: X-API-Key header에 유효한 key가 포함되어 있는지 확인하십시오. 필요한 경우 Dashboard에서 새 key를 생성하십시오.

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 Limits를 참조하십시오.

500: Server Error

서버 측에 문제가 발생했습니다.

해결 방법: 잠시 후 request를 다시 시도하십시오. 오류가 지속되면 status page를 확인하거나 실패한 response의 X-Foura-Request-Id를 첨부하여 지원 팀에 문의하십시오.

503: Service Disabled or At Capacity

503 오류는 서비스가 점검을 위해 일시적으로 중단되었거나 동시성 제한(concurrency limit)에 도달했음을 의미합니다. 두 response 모두 retryAfter 필드를 포함합니다. 동시성 제한인 경우에는 currentlimits 필도 포함됩니다.

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

해결 방법: retryAfter 초 동안 대기한 후 다시 시도하십시오. 진행 중인 점검 일정은 status page에서 확인할 수 있습니다.

Target-Side Failures Inside 200 OK

모든 실패가 2xx가 아닌 HTTP status로 나타나는 것은 아닙니다. 대상 사이트가 오류 페이로드와 함께 HTTP 200을 반환하는 경우, FourA는 여전히 body를 전달하지만 해당 request를 application_error로 분류합니다. 대상 사이트가 귀사의 validate 규칙에서 허용하지 않는 2xx가 아닌 상태 코드를 반환하는 경우, 결과는 application_fail이 되며 body는 변경되지 않은 상태로 전달됩니다.

두 경우 모두 네트워크 수준에서 request가 성공한 것처럼 요금이 부과됩니다. 전체 분류 체계는 Outcomes 참조 문서를 확인하십시오.

Response 인코딩

FourA는 response body를 UTF-8로 자동 디코딩합니다. 대상 사이트가 windows-1251, gbk, shift_jis, iso-8859-* 또는 Content-Type header나 HTML <meta charset> 태그에 선언된 기타 문자 집합을 제공하는 경우, data(single, proxy) 또는 body(browser) 필드에서 깨끗한 UTF-8 문자열을 받게 됩니다.

바이너리 페이로드(이미지, protobuf, 원시 오디오)의 경우 request에 returnBuffer: true를 설정하십시오. body는 문자 집합 변환이 적용되지 않은 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:
            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년 6월 18일