Files
llm-wiki/wiki/concepts/api-error-envelope-design.md

12 KiB

title, source_type, status, confidence, tags, related_projects, last_reviewed
title source_type status confidence tags related_projects last_reviewed
API Error Envelope 설계 (custom vs ProblemDetail vs rpc.Status) llm-generated draft medium
api-design
error-handling
http
ca-skeleton
2026-05-22

API Error Envelope 설계 (custom vs ProblemDetail vs rpc.Status)

Layer: wiki/concepts/ — 일반 개념. 특정 프로젝트의 결정/구현 사실은 wiki/projects/에서 다룬다.

Summary

API error envelope은 실패 응답의 구조 계약이다. 표준 후보는 RFC 7807 ProblemDetail, Google rpc.Status, JSON:API errors, GraphQL errors가 있고, 그 외 대형 서비스의 custom envelope (Stripe / GitHub / 토스페이먼츠 등)이 사실상 진영별 컨벤션으로 자리잡았다. 설계 결정의 핵심 축은 (a) 성공/실패 응답의 대칭 여부, (b) code · category · retryable 같은 운영 메타데이터의 1급 필드 승격 여부, (c) 표준 lock-in과 client SDK 호환성의 trade-off다.

Standard (공식 정의)

RFC 7807 ProblemDetail (실패 전용 평면)

IETF 표준. application/problem+json media type. 필드: type (URI), title, status, detail, instance. 모든 필드 optional이고 확장은 top-level에 임의 필드 추가로 한다. RFC 9457로 obsolete되었지만 의미상 호환이며, Spring 6+는 ProblemDetail 클래스로 기본 지원한다. 성공 응답에는 적용되지 않고 실패 전용 평면 shape이다.

Google rpc.Status (gRPC, typed details)

Google AIP-193. code (정수, google.rpc.Code enum), message, details: Any[]. detailsgoogle.protobuf.Any로 packing되며 표준 detail 타입(ErrorInfo, LocalizedMessage, Help, RetryInfo, QuotaFailure, BadRequest)을 포함한다. RetryInfo로 retryable + delay까지 표준화되어 있다. REST/gRPC 양쪽에 동일 모델로 매핑된다.

JSON:API errors (배열)

JSON:API v1.1 spec. top-level에 errors: [] array 필수. 각 error 객체는 id, links, status, code, title, detail, source.pointer (JSON Pointer), meta 중 하나 이상을 가진다. source.pointer로 form 필드 단위 오류를 가리킨다.

GraphQL errors (HTTP 200 + errors field)

GraphQL Specification (October 2021) §7.1.2. 응답은 dataerrors를 모두 가질 수 있고, error 객체는 message (required), locations, path, extensions를 가진다. transport는 보통 HTTP 200이고 4xx/5xx는 transport-level 실패에만 사용한다.

진영별 custom envelope (표준 아님)

  • Stripe: { error.{ type, code, decline_code, message, param, doc_url, ... } }. type enum이 사실상 category 역할.
  • GitHub: { message, documentation_url, errors[].{ resource, field, code } }. validation 항목별 풀이가 명시적.
  • 토스페이먼츠: { code, message }. 가장 얇은 envelope. retryable/category는 code semantic으로 추론.

이 세 사례는 어떤 IETF/W3C 표준도 따르지 않으며, 각 회사 SDK가 envelope을 흡수하는 전제로 동작한다.

한계 / 주의점

Custom envelope

  • 외부 표준이 존재하지 않으므로 client SDK를 직접 작성하거나 envelope 처리 규칙을 client에게 명시적으로 전달해야 한다.
  • 성공/실패 대칭, retryable 1급 같은 운영 친화 결정을 자유롭게 둘 수 있지만 그 비용은 "표준 client 라이브러리 0개"다.

RFC 7807 ProblemDetail

  • 실패 전용 평면 shape이므로 "성공도 envelope으로 감싸 success: true/false로 분기하고 싶다"는 요구와 구조적으로 충돌한다.
  • code 필드가 표준에 없다 — type URI가 식별자다. 짧은 머신리더블 코드를 원하면 확장 필드를 강제해야 하고, 결국 "표준 위에 사실상 custom 레이어"가 된다.
  • Spring 6+는 기본 활성이므로, custom envelope을 채택한다는 것은 의식적으로 표준 인프라를 비활성화하는 선택이다.
  • application/problem+json을 content-negotiation으로 처리하는 client는 흔하지 않다 — 실질 호환성 이득은 명목 수준에 가깝다.

Google rpc.Status

  • 본질적으로 gRPC/protobuf 생태계 결합이다. HTTP REST 전용 서비스에 강제하면 Any 디코딩 부담이 client에 mismatch로 전가된다.
  • 표준 detail 타입 카탈로그를 알아야 효용이 발휘되어 학습 곡선이 높다.
  • 가벼운 CRUD API에는 과한 표현력이다.

JSON:API errors

  • errors[] array와 source.pointer는 항목 단위 오류 표현에 강하지만, category/retryable이 1급 필드가 아니라 meta로 빠진다.
  • 부분 채택 시 표준성이 사라진다. 완전 채택 시 success response 리소스 객체 구조, sparse fieldsets 등 spec 전체에 lock-in된다.

GraphQL errors

  • HTTP 200 + errors field가 transport 규약이라 CDN / proxy / observability 도구의 4xx/5xx 기반 알람·캐시·라우팅과 부조화한다.
  • partial success가 1급 개념이라 REST envelope과 패러다임 자체가 다르다 — REST 컨텍스트에서 직접 비교해 "GraphQL이 옳다/그르다"라고 말할 수 없다.

흔한 오해

  • "Stripe / GitHub / 토스페이먼츠가 그렇게 하니까 industry standard다" — 표준이 아니라 진영별 컨벤션이다. SDK 없이 직접 다루는 client는 거의 없다는 전제 위에서 동작한다.
  • "ProblemDetail은 잘못된 설계다" — 실패 전용 use case (예: 외부 노출 API, RFC 9457 client 생태계 활용)에서는 유효한 선택이다.

Project Application

위 branch-note들은 success / error 대칭, error.code · error.category · error.retryable · error.details 분리, meta.requestId / meta.traceId / meta.correlationId 1급 노출, raw exception / SQL / token / body의 응답 leak 금지를 계약으로 둔다.

Claim-backed Knowledge

이 개념 문서의 핵심 설명은 raw source claim 으로 뒷받침되어야 한다. 공식 문서 claim, 회사 사례 claim, 내 프로젝트 decision 을 분리한다.

Knowledge Point Supporting Claims Confidence Notes
RFC 7807 ProblemDetail은 application/problem+json 기반 실패 전용 평면 shape이며 type URI가 식별자다 (code 필드 없음) raw/official-docs/problem-detail-rfc-7807, raw/official-docs/spring-problem-detail high 공식 표준 (IETF / Spring) — success/error 대칭·머신리더블 code 요구와 구조적으로 충돌
Google rpc.StatusRetryInfo 등 typed detail로 retryable + delay까지 표준화 (REST/gRPC 공통 모델) raw/official-docs/google-api-error-format high 공식 vendor 문서(AIP-193) — 단 protobuf/Any 결합이라 HTTP REST 전용에는 과한 표현력
JSON:API는 errors[] + source.pointer(JSON Pointer)로 항목 단위 오류를 가리키지만 category/retryable이 1급 필드가 아니다 raw/official-docs/json-api-errors-spec high 공식 표준 — 부분 채택 시 표준성 상실, 완전 채택 시 spec 전체 lock-in
Stripe/GitHub/토스페이먼츠 envelope은 IETF/W3C 표준이 아니라 진영별 컨벤션이며 각 사 SDK가 envelope을 흡수하는 전제로 동작한다 raw/company-tech-blogs/stripe-error-format, raw/company-tech-blogs/github-api-error-format, raw/company-tech-blogs/toss-payments-error-format medium company-case-study — 공식 best practice로 일반화 금지. SDK 부재 client는 거의 없다는 전제

내가 설명할 수 있어야 하는 것

  • API error envelope의 후보 표준(RFC 7807 / Google rpc.Status / JSON:API / GraphQL errors)의 공식 정의와 각자의 식별자 표현 방식은?
  • 어떤 문제를 해결하는가 — client가 실패를 어떻게 분기·재시도·관측 가능하게 만드는 구조 계약인가?
  • 어떤 상황에서는 custom envelope을 쓰면 안 되는가(표준 client 생태계 활용이 우선인 외부 노출 API 등)?
  • 공식 표준이 말하지 않는 부분(success/error 대칭, retryable·category 1급화)은 무엇이고 그 비용("표준 client 라이브러리 0개")은 무엇인가?
  • Stripe/GitHub/토스 사례를 industry standard처럼 일반화하면 안 되는 지점은?
  • 내 프로젝트에서는 어떤 branch decision(custom envelope 채택 + ProblemDetail 거부)으로 연결됐는가?
  • 이 개념을 코드/운영에서 검증하려면 무엇을 확인해야 하는가(envelope 직렬화, leak 금지, ProblemDetail 비활성 build-time 강제 등)?

Interview Questions

  • 왜 RFC 7807 ProblemDetail을 채택하지 않았는지? 표준을 우회한 비용은 무엇이고, 그 대신 무엇을 얻는지?
  • retryable을 1급 필드로 둔 이유는? client는 retryable: true를 받았을 때 어떻게 다르게 동작해야 하는지?
  • validation error를 error.details에 담을 때 GitHub errors[].{resource, field, code} 또는 JSON:API source.pointer와 비교하면 어떤 형식을 택했고, 왜 그렇게 택했는지?
  • error.codeerror.category를 분리한 이유는? client 분기는 어느 쪽으로 하라고 가이드하는지?
  • 응답에 절대 leak하면 안 되는 항목은? exception class name, stack trace, SQL, token, raw body, upstream raw error body 각각이 왜 금지인지 설명할 수 있는지?

Do Not Overclaim

  • "내 envelope이 표준이다" / "ca-tmpl envelope이 IETF 표준 envelope이다"라고 말하면 안 된다. 어떤 표준도 success/error 대칭 + retryable 1급 + category 1급을 동시에 강제하지 않는다 — 자체 결정일 뿐이다.
  • "ProblemDetail은 잘못된 설계다"라고 단정하면 안 된다. 실패 전용 평면이라는 그 자체가 결함이 아니며, 외부 표준 client 호환을 우선하는 use case에서는 합리적이다.
  • "Stripe / GitHub / 토스가 다 custom이니까 표준은 의미 없다"라고 말하면 안 된다. 그들은 SDK가 envelope을 흡수하는 전제 위에 동작하며, 표준 미준수가 정당화되는 것이 아니라 trade-off가 다른 것뿐이다.
  • Google rpc.StatusRetryInfo.retry_delay보다 retryable: boolean이 우월하다고 주장하면 안 된다 — 후자는 단순하지만 actionable한 delay 정보를 잃는다.

Sources

공식 표준

진영별 사례 (표준 아님)

Canonical (프로젝트 결정 사실)

Cluster / 묶음