ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [OpenAI][Responses API] response.failed와 response.incomplete를 수신 감사 로그에서 어떤 TTL과 상태 필드로 나누나
    기타개발지식/풀스택개발 2026. 7. 9. 20:17

    IT 리서치 노트

    [OpenAI][Responses API] response.failed와 response.incomplete를 수신 감사 로그에서 어떤 TTL과 상태 필드로 나누나

    Responses API를 background mode와 webhook으로 운영하면 completed가 아닌 상태를 한 바구니에 넣고 실패라고 적어 두기 쉽다. 하지만 2026년 7월 9일 기준 OpenAI 공식 문서를 다시 보면, webhook에는 response.failed와 response.incomplete가 따로 있고, retrieve 응답에서는 error와 incomplete_details.reason도 별도 필드로 내려온다. 여기에 background mode의 polling용 보관 창은 대략 10분이다. 이 글은 수신 감사 로그에서 failed와 incomplete를 어떤 상태 필드와 TTL 기준으로 나눠 두는 편이 실무적으로 맞는지 정리한 것이다.

    1. 개요

    결론부터 말하면 response.failed와 response.incomplete는 같은 미완료 상태로 묶지 말고 receipt type, 원인 필드, TTL 클래스를 따로 두는 편이 맞다. failed는 error 중심이고, incomplete는 incomplete_details.reason 중심이기 때문이다. 또 OpenAI의 polling용 response data 보관 창은 대략 10분이므로, 플랫폼 retrieve 가능 기간과 앱의 장기 감사 로그 보관 기간도 같은 값으로 두지 않는 편이 좋다.

    이미 retrieve 재조회와 감사 로그 분리 글이 receipt와 result commit 순서를 다뤘고, event.id와 response_id 보존 기준 글이 키를 나눴다면, 이번 글은 그 위에 얹는 terminal state 분기 규칙이다. 반대로 failed 수신 뒤 webhook body에는 error가 없고 retrieve 결과만 남는 운영 단계가 궁금하다면, 이번 글의 후속편인 response.failed 뒤 재조회 closure 단계 글로 바로 이어서 보는 편이 좋다.

    2. 어디서 실제로 막히는가

    현장에서 자주 꼬이는 지점은 세 가지다. 첫째, completed가 아니면 전부 failed로 찍어 나중에 max_output_tokens 부족과 실제 서버 오류를 같은 유형으로 보게 된다. 둘째, webhook event type은 남겼지만 retrieve 시점의 error 또는 incomplete_details.reason를 저장하지 않아 재현 질문이 길어진다. 셋째, OpenAI의 짧은 polling 창과 앱 쪽 장기 장애 분석 창을 같은 TTL로 둬 receipt는 남았는데 result 원인은 이미 지워지거나, 반대로 불필요한 raw body가 너무 오래 남는다.

    OpenAI webhook 타입 레퍼런스는 response.failed와 response.incomplete를 별도 이벤트로 정의한다. retrieve 레퍼런스는 failed일 때 error를, incomplete일 때 incomplete_details.reason를 제공한다. background guide는 queued와 in_progress를 벗어나면 terminal state라고 적고, polling용 response data는 대략 10분 보관한다고 설명한다. 이 세 문장을 같이 읽으면 상태 필드를 하나로 줄이면 안 된다는 결론이 자연스럽게 나온다.

    특히 사람 승인, 재시도 큐, 비용 경보, content filter 후처리가 섞인 팀에서는 failed와 incomplete가 같은 질문을 만들지 않는다. failed는 장애 원인과 재시도 판단이 중심이고, incomplete는 예산 상한 또는 정책 차단이 중심이다. 질문이 다른데 저장 필드가 같으면 보고서가 금방 흐트러진다.

    • 증상: completed가 아닌 모든 응답을 같은 실패 코드로 묶는다.
    • 실패: webhook 타입만 남기고 retrieve 시점 원인 필드를 안 남긴다.
    • 막힘: polling용 플랫폼 보관 창과 앱 장기 감사 창을 같은 TTL로 둔다.
    • 누락: failed는 error, incomplete는 incomplete_details.reason라는 구분을 로그 스키마에 반영하지 않는다.
    질문 먼저 볼 필드 이유
    진짜 서버 오류였나 error.code failed 분기다
    예산이나 filter 때문에 중간 종료됐나 incomplete_details.reason incomplete 분기다
    retrieve를 얼마 동안 기대할 수 있나 background 보관 창 대략 10분 polling 전제다

    3. 실무에서 적용하는 순서

    가장 덜 꼬이는 구조는 세 레이어다. 첫째, receipt log에는 webhook-id, event.id, response_id, event type을 남긴다. 둘째, terminal state log에는 failed면 error.code와 error.message, incomplete면 incomplete_details.reason와 output 예산 맥락을 남긴다. 셋째, TTL은 incident, budget_review, short_retrieve_fallback처럼 목적별 클래스로 나눈다.

    1. receipt에는 event type과 response_id를 남긴다.
    2. retrieve 후 terminal state를 한 번 더 기록한다.
    3. failed는 error, incomplete는 incomplete_details를 필수 필드로 둔다.
    4. TTL은 장애 분석용, 예산 검토용, 짧은 polling용으로 나눈다.
    5. 운영 문서에는 상태 필드 예시를 표로 같이 남긴다.

    이 구조가 좋은 이유는 같은 response_id라도 운영 질문이 서로 다르기 때문이다. 예를 들어 incomplete reason이 max_output_tokens면 prompt나 상한값 조정이 먼저고, failed error가 rate_limit_exceeded면 retry backoff와 호출량 관찰이 먼저다. terminal state 문자열만 같게 저장하면 이런 분기가 전부 뒤늦게 수작업으로 풀린다.

    운영 메모 예시
    receipt_type=response.failed|response.incomplete
    status_bucket=terminal_error|terminal_incomplete
    failed_error_code=rate_limit_exceeded|null
    incomplete_reason=max_output_tokens|null
    retrieve_fallback_window=about_10m
    audit_ttl_class=incident|budget_review

    이렇게 적어 두면 운영자가 receipt만 보고도 어느 재조회 규칙과 어느 TTL 표를 봐야 하는지 바로 안다. background mode에서는 terminal state가 되었는지, 그리고 그 terminal state가 어떤 종류인지의 구분이 핵심이다.

    4. 공식 문서와 예시 화면으로 확인하기

    첫 자료는 OpenAI webhook 타입 레퍼런스의 failed 이벤트 구간이다. 여기서는 response.failed가 별도 webhook 타입으로 존재하고 payload 안 data.id가 모델 response ID라는 점을 먼저 고정해야 한다.

    OpenAI webhook 타입 레퍼런스는 <code>response.failed</code> 이벤트와 <code>data.id</code> 구조를 따로 보여 준다.
    OpenAI webhook 타입 레퍼런스는 <code>response.failed</code> 이벤트와 <code>data.id</code> 구조를 따로 보여 준다.

    즉 감사 로그에서 failed는 단순히 completed가 아니었던 상태가 아니라, 실패 전용 receipt 분기다. 이후 상태 필드를 terminal_error 계열로 닫을지, 재시도 가능한 실패로 둘지 판단할 때 이 receipt가 출발점이 된다.

    두 번째 자료는 incomplete 이벤트다. OpenAI는 background response가 interrupted 되었을 때 response.incomplete를 별도 event로 보낸다.

    incomplete webhook receipt와 retrieve 결과를 같이 적어 두는 예시 화면이다.
    incomplete webhook receipt와 retrieve 결과를 같이 적어 두는 예시 화면이다.

    이 구분이 중요하다. incomplete는 같은 최종 상태처럼 보여도, 원인 필드와 후속 운영 방식이 failed와 다르다. 하나는 에러 중심으로 닫히고, 다른 하나는 incomplete reason과 예산 또는 content filter 경계를 같이 남겨야 한다.

    세 번째 자료는 response retrieve 레퍼런스다. 여기서는 incomplete_details.reason과 error가 서로 다른 필드라는 점을 확인해야 한다.

    Responses retrieve 레퍼런스는 <code>error</code>와 <code>incomplete_details.reason</code>를 따로 내려준다고 설명한다.
    Responses retrieve 레퍼런스는 <code>error</code>와 <code>incomplete_details.reason</code>를 따로 내려준다고 설명한다.

    그래서 receipt 로그에 상태 문자열만 적으면 부족하다. incomplete면 reason을, failed면 error code와 message를 남기는 식으로 필드 구성이 갈라져야 나중에 TTL과 보존 범위를 다르게 잡을 수 있다.

    네 번째 자료는 background mode 가이드다. OpenAI는 polling을 위해 response data를 대략 10분 보관한다고 설명하고, queued나 in_progress를 벗어나면 terminal state라고 적고 있다.

    OpenAI background 가이드는 polling용 response data 보관이 대략 10분이고 terminal state를 별도로 본다고 설명한다.
    OpenAI background 가이드는 polling용 response data 보관이 대략 10분이고 terminal state를 별도로 본다고 설명한다.

    이 값은 앱의 장기 감사 로그 TTL이 아니다. failed와 incomplete 모두 receipt는 오래 남길 수 있지만, OpenAI retrieve fallback 창은 훨씬 짧다. 플랫폼 보관 창과 앱 운영 창을 같은 숫자로 두면 사고 복기가 애매해진다.

    실무에서는 이벤트 타입과 저장 필드를 표로 먼저 나눠 두는 편이 빠르다. response.failed와 response.incomplete는 같은 미완료 묶음이 아니라, 저장해야 하는 원인 필드와 만료 기준이 다르다.

    failed와 incomplete를 receipt type, 원인 필드, TTL 기준으로 나눈 점검표다.
    failed와 incomplete를 receipt type, 원인 필드, TTL 기준으로 나눈 점검표다.

    이 표를 써 두면 운영자가 status=terminal_error만 적고 끝내는 실수를 줄일 수 있다. 이미 event.id와 response_id 보존 기준 글이 키 분리를 다뤘다면, 이번 표는 상태 필드 분리를 더하는 후속 규칙이다.

    마지막 자료는 상태 필드 스키마 예시다. receipt와 result commit을 나누되, failed와 incomplete에서 남기는 세부 원인 필드를 다르게 두면 운영 표가 훨씬 짧아진다.

    failed와 incomplete에서 저장 필드를 다르게 두는 감사 로그 예시다.
    failed와 incomplete에서 저장 필드를 다르게 두는 감사 로그 예시다.

    이 구조를 쓰면 며칠 뒤 특정 response가 실패였는지 예산 중단이었는지 곧바로 갈린다. 또 webhook-id와 response_id 기준 글과 같이 보면 중복 차단과 상태 닫기를 한 세트로 문서화하기 쉽다.

    5. 주의사항과 리스크

    첫 번째 리스크는 failed와 incomplete를 같은 장애 카테고리로 묶어 재시도 정책을 잘못 가져가는 것이다. 두 번째 리스크는 retrieve 시점 필드를 저장하지 않아 webhook body만으로 원인을 추측하게 되는 것이다. 세 번째 리스크는 OpenAI의 짧은 polling 창을 앱 쪽 장기 감사 정책으로 오해하는 것이다.

    운영 전에 확인할 때는 최소한 event type, terminal state, error code, incomplete reason, audit TTL class를 같은 로그에 남기는 편이 좋다. 이 다섯 칸만 분리돼도 background receipt 복기가 훨씬 빨라진다.

    • response.failed는 장애 원인 중심으로 본다.
    • response.incomplete는 reason과 예산 맥락 중심으로 본다.
    • platform polling 창과 앱 감사 창은 같은 값이 아니다.

    6. 결론

    OpenAI Responses API에서 response.failed와 response.incomplete를 같은 미완료 상태로 묶으면 receipt는 남아도 운영 질문이 풀리지 않는다. failed는 error 중심, incomplete는 incomplete_details.reason 중심으로 필드를 나누고, TTL도 장애 분석용과 예산 검토용으로 분리하는 편이 맞다. 여기에 OpenAI의 대략 10분 polling 창을 별도 메모로 두면 수신 감사와 사후 분석이 한층 덜 꼬인다.

    • receipt type은 terminal state 해석의 출발점이다.
    • failed와 incomplete는 서로 다른 원인 필드를 가진다.
    • TTL은 receipt 목적에 따라 다르게 둔다.

    7. 참고 링크

    1. https://developers.openai.com/api/reference/typescript/resources/webhooks/
    2. https://developers.openai.com/api/docs/guides/background
    3. https://developers.openai.com/api/reference/resources/responses/methods/retrieve/
    4. https://developers.openai.com/api/docs/guides/webhooks
Designed by Tistory.