파이썬 API가 200을 반환해도 실패일 수 있습니다: 응답 검증 순서

파이썬 API가 HTTP 200을 반환하고 response.json()까지 성공해도 실제 업무 처리가 끝났다고 단정할 수는 없습니다. HTTP 상태는 통신 결과에 가깝고, JSON 파싱은 형식 확인이므로 API가 정의한 code, status, 필수 data를 별도로 확인해야 합니다. 이 글에서는 HTTP 상태 확인부터 본문 구조와 업무 결과 판정, 빈 결과와 부분 결과의 기록까지 운영에서 재사용할 수 있는 순서를 정리합니다.

HTTP 200과 업무 성공은 확인 대상이 다릅니다

RFC 9110에서 200 OK는 성공 응답이고, 4xx는 클라이언트 오류, 5xx는 서버 오류로 분류됩니다. 다만 여기서 말하는 성공은 HTTP 요청에 대한 의미입니다. 서버가 요청을 받아 응답했다는 사실과, 요청한 업무가 정상 처리됐다는 사실은 API 설계에 따라 분리될 수 있습니다.

실제로 서비스에 따라 연계 오류가 있어도 HTTP 200을 반환하고, 응답 본문의 code로 실제 처리 결과를 구분하게 합니다. 스마트 APC API 가이드와 카카오 쇼핑 Open API 문서도 HTTP 상태만 보지 말고 API별 응답 바디의 코드와 메시지를 확인하는 방식을 안내합니다.

예를 들어 조회 결과를 저장하는 자동화라면, 다음처럼 raise_for_status()만 통과한 결과를 곧바로 성공으로 저장하면 위험합니다. 아래 코드는 설명용 조각이며 timeout_seconds와 저장 함수는 사용하는 환경에 맞게 정해야 합니다.

response = requests.get(url, timeout=timeout_seconds)
response.raise_for_status()
result = response.json()

# 여기서 바로 성공 처리하면 본문 code와 필수 data를 놓칠 수 있습니다.
save_result(result)

raise_for_status()는 HTTP 오류 상태를 확인하는 단계이고, response.json()은 응답을 JSON으로 읽는 단계입니다. 둘 다 필요한 검사지만 어느 쪽도 API의 업무 성공 코드를 대신 판정하지 않습니다.

응답 검증은 이 순서로 나눠 보세요

1. HTTP 상태와 호출 예외를 먼저 기록합니다

타임아웃이나 연결 오류처럼 응답 자체를 받지 못한 경우와 4xx·5xx 응답은 본문 업무 실패와 다른 종류입니다. 상태 코드와 요청 식별에 필요한 안전한 정보부터 남기고, 재시도 여부는 해당 API의 정책과 작업의 중복 가능성을 따로 판단합니다.

response = requests.get(url, timeout=timeout_seconds)
response.raise_for_status()

RFC 9110의 분류상 4xx는 클라이언트 오류, 5xx는 서버 오류입니다. 그렇다고 모든 4xx·5xx를 같은 방식으로 재시도해야 한다는 뜻은 아닙니다. 인증, 입력값, 서버 일시 오류는 복구 방법이 다르므로 로그에서 구분해 두는 편이 안전합니다.

2. JSON 형식과 최상위 구조를 확인합니다

응답이 JSON이어야 한다는 API라면 파싱 예외를 업무 성공으로 기록하지 않도록 합니다. 응답이 JSON이 아니거나 204처럼 본문이 없는 경우에는 JSONDecodeError가 발생할 수 있으므로, 해당 API가 본문을 반환해야 하는 호출인지 먼저 명세를 확인합니다.

반대로 204 No Content는 본문이 없는 성공 응답이므로, JSON 본문이 항상 있어야 하는 API라면 별도 분기로 다뤄야 합니다. 즉, 204를 무조건 실패로 기록하거나 무조건 빈 JSON으로 바꾸기보다 엔드포인트 명세에 맞춰 처리해야 합니다.

try:
    body = response.json()
except ValueError:
    record_failure(kind="invalid_json", status=response.status_code)
    raise

if not isinstance(body, dict):
    record_failure(kind="unexpected_shape", status=response.status_code)
    raise ValueError("API 응답 최상위 구조가 객체가 아닙니다.")

3. code와 status는 API 명세의 성공값으로 비교합니다

필드 이름과 성공 코드 값은 API마다 다릅니다. 예를 들어 code == 200이라는 가상의 규칙을 모든 API에 복사하지 말고, 해당 API 문서가 정한 성공 조건을 설정이나 별도 어댑터에 명시합니다.

SUCCESS_CODES = {"OK"}  # 실제 값은 해당 API 명세로 교체합니다.

business_code = body.get("code")
business_status = body.get("status")
message = body.get("message")

if business_code not in SUCCESS_CODES:
    record_failure(
        kind="business_error",
        status=response.status_code,
        code=business_code,
        api_status=business_status,
        message=message,
    )
    raise RuntimeError(f"API 업무 오류: {message}")

스마트 APC는 응답 본문의 code와 message를 정의하고 code로 연계 결과를 구분합니다. 다른 API에서는 해당 명세에서 상태 필드나 오류 객체의 구조를 확인해야 합니다. 공통 함수가 필드 이름을 추측하게 만들기보다 API별 검증 어댑터를 두는 방식이 변경에 덜 취약합니다.

4. 성공일 때만 필수 data를 검증합니다

본문 코드가 성공이어도 후속 저장에 필요한 data가 없으면 자동화 작업은 완성되지 않습니다. data가 객체인지 목록인지, 반드시 있어야 하는 하위 필드가 무엇인지는 API 명세와 업무 목적에 맞춰 확인합니다.

data = body.get("data")
if data is None:
    record_failure(kind="missing_data", code=business_code)
    raise ValueError("성공 응답에 필요한 data가 없습니다.")

이 검사는 data가 비어 있지 않은지를 무조건 확인하는 규칙과는 다릅니다. 예를 들어 검색 결과가 없는 빈 목록은 정상일 수 있지만, 다음 작업에 데이터가 반드시 필요하다면 저장을 보류하고 명세와 요청 조건을 확인해야 합니다. 빈 결과를 정상으로 볼지는 그 API의 명세와 다음 단계가 요구하는 조건으로 결정하고, 판정 이유를 로그에 남깁니다.

5. 빈 결과와 부분 결과를 별도 상태로 남깁니다

성공, 업무 실패, 데이터 없음, 부분 결과를 모두 Boolean 하나로 합치면 나중에 재처리 대상을 고르기 어렵습니다. 예를 들어 다음처럼 결과 상태를 나누면 운영자가 원인을 구별할 수 있습니다.

def classify_data(data):
    if isinstance(data, list) and not data:
        return "empty_result"
    if isinstance(data, dict) and data.get("partial") is True:
        return "partial_result"
    return "success"

result_state = classify_data(data)
record_result(
    state=result_state,
    http_status=response.status_code,
    business_code=business_code,
)

위 코드는 상태를 나누는 가상 예시이며, record_result는 실제 기록 함수로 구현해야 합니다. partial 같은 필드와 성공 판정 조건은 API 명세와 업무 요구에 맞게 바꿔야 합니다. 빈 결과나 부분 결과를 전체 성공으로 확정할지는 별도로 결정합니다. 페이지네이션이나 처리 보장이 있는 API라면 한 번의 응답만 보고 전체 성공으로 기록하지 말고, 명세가 요구하는 다음 페이지나 완료 상태를 확인해야 합니다.

검증 결과를 로그에서 구분하는 방법

운영 로그에는 적어도 HTTP 상태, 본문 업무 코드, 결과 상태를 서로 다른 필드로 남기는 편이 좋습니다. 그러면 다음과 같은 질문에 답하기 쉬워집니다.

  • 응답을 받지 못한 네트워크 실패인가?
  • HTTP는 성공했지만 API의 업무 코드가 실패인가?
  • 업무 코드는 성공했지만 필수 데이터가 없는가?
  • 정상적인 빈 결과인가, 재처리해야 하는 부분 결과인가?

메시지에는 API가 반환한 설명을 보존하되 인증 정보나 개인정보가 섞이지 않도록 주의합니다. 재시도는 business_error라는 이름만 보고 일괄 실행하지 말고, 오류 코드의 의미와 작업의 멱등성, API의 호출 제한을 함께 확인해야 합니다.

최종 체크 순서

자동화 코드에 성공 판정을 넣기 전에는 다음 순서를 기준으로 점검하면 됩니다.

  1. 응답을 받았는지와 네트워크 예외를 구분합니다.
  2. HTTP 상태를 raise_for_status() 또는 status_code로 확인합니다.
  3. 본문이 필요한 호출인지 확인한 뒤 JSON 형식과 최상위 구조를 검증합니다.
  4. API 명세의 code·status·오류 객체를 실제 성공값과 비교합니다.
  5. 성공 응답의 필수 data, 빈 결과, 부분 결과 조건을 확인합니다.
  6. HTTP 실패, 업무 실패, 데이터 없음, 정상 성공을 서로 다른 상태로 기록합니다.

결국 “HTTP 200인가?”는 첫 번째 질문일 뿐입니다. “이 API가 정의한 업무 성공 조건을 만족했고, 다음 작업에 필요한 데이터가 있는가?”까지 확인해야 자동화 결과를 성공으로 저장할 수 있습니다.

함께 확인할 글

AI API의 JSON 응답 구조 자체를 더 엄격하게 검증해야 한다면 파이썬 AI JSON 응답 오류 줄이기: JSON 모드와 Structured Outputs, Pydantic 검증 구분법에서 응답 형식 검증과 Pydantic의 역할을 이어서 살펴볼 수 있습니다.

참고 자료

Leave a Comment