API 에러 계약 정책¶
문서 역할¶
- 역할:
규범 - 문서 종류:
policy - 충돌 시 우선 문서: 실패
ErrorData/taxonomy는 이 문서, 공통 JSON API 응답 envelope은api-response-contract-policy.md - 기준 성격:
as-is
적용 범위¶
- coupler-api
- coupler-admin-web
- coupler-mobile-app
목적¶
이 문서는 API 실패 응답의 ErrorData와 error taxonomy 최종 계약을 정의한다. 공통 JSON API 응답 envelope은 API 공통 응답 계약 정책을 단일 기준으로 따른다.
- 서버는 하나의 canonical
ERROR_CATALOG를 실패 원인 SoT로 둔다. - 서버 production callsite는
ErrorDescriptor를 전달한다. error_code는 descriptor 안의 wire field이며, 서버 callsite API가 아니다.- Mobile/Admin은 서버 catalog에서 생성된 runtime contract만 사용한다.
- 계약된 JSON API 실패는 공통 응답 envelope의
{ ok: false, error: ErrorData }로 반환한다. - HTTP non-2xx는 API error taxonomy 밖의 transport/protocol/proxy 실패로만 사용한다.
- transition 계층, 중간 산출물, raw
error_codehelper는 최종 구조에 둘 수 없다.
잔여 부채와 배포 순서는 기술부채 문서에서 관리한다. 이 문서는 최종 상태만 판정한다.
용어¶
ErrorDescriptor: 하나의 실패 원인을 나타내는 catalog entry. 최종 authoring field는code,source,surfaces,action,messageKey,messageArgContextKeys뿐이다.ERROR_CATALOG: 서버의 canonical descriptor map. API 에러 계약의 단일 SoT다.ErrorData: 실패 envelope의error객체.error_code,error_source,error_action,error_context,request_id를 포함한다.error_code:ErrorData.error_code로 노출되는 wire failure cause. descriptor의code값이다.error_source:ErrorData.error_source로 노출되는 실패 원인의 상위 서버 도메인 또는 안정적인 모듈. 세부 기능, operation, 화면, flow, 판정 상태가 아니다.error_surfaces: 해당 실패가 영향을 주는 클라이언트 surface 목록. 응답 JSON 필드가 아니라 계약 metadata다.error_action: 클라이언트가 취해야 할 기본 처리 방식.error_context: client 표시 메시지 인자에 필요한 공개 보조 정보. descriptor의messageArgContextKeysallowlist에 포함된 값만 wire 응답에 남기고, 내부 진단값은 서버 로그로만 남긴다.request_id: 요청 추적용 식별자.
최종 아키텍처¶
flowchart LR
A["API domain code"] --> B["response_error(res, descriptor, context?)"]
B --> C["buildErrorData(descriptor, context?)"]
C --> D["HTTP 200 JSON: ok=false, error: ErrorData"]
E["ERROR_CATALOG"] --> B
E --> F["Generated Mobile/Admin runtime contract"]
F --> G["Client operation handling"]
- API 서버는 실패 응답 경계에
ErrorDescriptor와 필요한ErrorContext만 전달한다. - 실패 응답 생성은
response_error/buildErrorData경계로 수렴한다. - Mobile/Admin은 생성된 runtime contract로
error_code를 검증하고, operation 소비 근거가 있는 경우에만 분기한다. - Swagger/OpenAPI 문서는 같은 catalog 기준으로 작성/검증된 실패 계약을 노출한다.
서버 코드 배치¶
Canonical catalog¶
coupler-api/lib/error/catalog/registry.ts에서 조립되는 ERROR_CATALOG는 API 에러 계약의 canonical SoT다. Descriptor entry는 coupler-api/lib/error/catalog/entries/*에 둘 수 있지만, 최종 계약은 조립된 ERROR_CATALOG 하나로 판정한다.
export const ERROR_CATALOG = {
MEMBER_AUTH_REVIEW_LIMIT_EXCEEDED: {
code: "MEMBER_AUTH_REVIEW_LIMIT_EXCEEDED",
source: "MEMBER",
surfaces: ["MOBILE_APP"],
action: "CONTACT_SUPPORT",
messageKey: "api.error.member.auth_review_limit_exceeded",
messageArgContextKeys: ["limit"],
},
} as const satisfies Record<string, ErrorDescriptor>;
허용 field:
code: stable wireerror_codesource: 상위 서버 도메인 또는 안정적인 모듈surfaces: 영향 받는 클라이언트 surface 목록action: 클라이언트 기본 처리 방식messageKey: 문서와 번역 연결용 keymessageArgContextKeys: 메시지 인자에 사용할 수 있는 context key allowlist
messageKey는 빈 문자열을 허용하지 않는다. 사용자에게 표시할 메시지가 없는 것처럼 보이는 legacy 실패도 안정적인 locale key를 지정해야 한다.
messageArgContextKeys에 선언한 key는 해당 실패 응답의 error_context에 문자열 또는 숫자로 반드시 있어야 한다.
messageArgContextKeys에 없는 key는 public error_context에 남기지 않는다. 공개 메시지 인자가 없는 descriptor의 error_context는 빈 객체다.
금지 field:
pathgroupnamecodePrefix- singular
error_surface - display message 문자열
- legacy numeric result code
- client-only routing state
Response boundary¶
최종 서버 경계는 descriptor-first다.
금지한다:
response_error의 두 번째 인자로 raw string을 전달하는 방식response_error의 두 번째 인자로 publicERROR_CODEalias를 전달하는 방식- callsite에서
error_code문자열을 직접 조합하거나 전달하는 방식 - callsite에서
{ error: { error_code, error_action, error_context } }payload를 직접 구성하는 방식
ERROR_CODE라는 public server alias는 최종 구조에 두지 않는다. error_code 문자열 목록이 필요하면 generator/test 내부 파생값으로만 둔다.
실패 ErrorData¶
API 실패 응답은 API 공통 응답 계약 정책의 JSON envelope을 따른다. 이 문서는 실패 envelope 안의 ErrorData 필드와 taxonomy를 정의한다.
{
"ok": false,
"error": {
"error_code": "MEMBER_AUTH_REVIEW_LIMIT_EXCEEDED",
"error_source": "MEMBER",
"error_action": "CONTACT_SUPPORT",
"error_context": {
"limit": 3
},
"request_id": "req_123e4567-e89b-42d3-a456-426614174000"
}
}
기본 규칙:
- 실패 응답은
ok: false와error: ErrorData를 포함한다. - 공통 응답 envelope, HTTP 200 실패 envelope, transport/protocol 예외는 API 공통 응답 계약 정책을 따른다.
- HTTP 4xx/5xx를
error_action/error_code로 변환하지 않는다. error.error_code는 descriptor의 stablecode에서 온다.error.error_source는 descriptor의source에서 온다.error.error_action은 descriptor의action에서 온다.error.error_context는 descriptor의messageArgContextKeys가 요구하는 공개 메시지 인자만 포함한다.error.error_context는 descriptor의messageArgContextKeys가 요구하는 문자열/숫자 값을 빠짐없이 포함해야 한다.error.request_id는req_+ UUID v4 형식이며, 서버 로그와 클라이언트 문의를 연결할 수 있어야 한다.ErrorData에는messageKey를 넣지 않는다. 클라이언트 표시는 generated runtime contract에서error_code로 descriptor의messageKey를 조회한 뒤 번역 리소스로 처리한다.
범위 예외:
- 파일 스트리밍, proxy pass-through, 네트워크/protocol 실패는 API 공통 응답 계약 정책의 예외 기준을 따른다.
- JSON API 계약 밖의 응답 경로는
ErrorData,error_code,error_action을 만들거나 client product flow 분기 기준으로 쓰지 않는다.
응답에 넣지 않는다:
- 권한/개인정보를 노출하는 내부 식별자, 내부 파일 경로, stack trace
- 개인정보 또는 인증 토큰
- 서버 내부 enum 이름
- 사용자가 바로 볼 문장형 메시지
- free-form parameter hint, field hint, operation source 같은 내부 진단 문자열
- migration/transition 판정값
Taxonomy¶
error_source¶
error_source는 실패 원인의 상위 서버 도메인 또는 안정적인 모듈이다. 세부 기능, operation, 화면, flow, 판정 상태를 source로 승격하지 않는다.
허용 기준:
- 도메인 경계를 나타낸다.
- UI surface나 operation 이름을 source로 쓰지 않는다.
- login, signup, token, review, profile edit처럼 operation 또는 flow를 나타내는 세부 명칭을 source로 쓰지 않는다.
- 너무 세분화된 임시 상태나 판정 단계를 source로 승격하지 않는다.
- 같은 서버 모듈에서 처리와 소유가 함께 이루어지는 단위로 둔다.
- 세부 기능명은
error_codesegment로 표현한다.
예시:
REQUEST_PARAMAUTHACCESS_CONTROLMEMBERMANAGERMATCHMATCHINGMEETINGREVIEW
현재 source ownership:
| Source | Ownership |
|---|---|
ACCESS_CONTROL |
공통 user/admin 접근 guard |
AUTH |
인증, 로그인, 토큰, 회원가입, 계정 인증 flow의 서버 실패 |
CRON |
배치/cron 작업의 서버 실패 |
LOUNGE |
라운지 도메인과 라운지 content/member moderation 실패 |
MANAGER |
관리자 계정, 권한, 관리자 상세 프로필 처리 실패 |
MATCH |
매치 entity, 매치 사용자 상태, 매치 리뷰/신고/직접 요청 실패 |
MATCHING |
매칭 가능 여부, 매칭 일정 제안/변경/횟수/날짜 제한 실패 |
MEETING |
미팅 entity, 참가/허용, 일정 충돌, 정원, 채팅방 준비 실패 |
MEMBER |
회원 프로필, 인증 심사, 프로필 심사, 추천, 휴면/수면, 매니저 선택 실패 |
PAYMENT |
결제/IAP 서버 실패 |
REQUEST_PARAM |
공통 request parameter 검증 실패 |
REVIEW |
여러 도메인에 걸친 심사 상태 contract/sync invariant 실패 |
SETTING |
운영 설정, 공지, 별칭, 가입 메시지, 고객지원 설정 실패 |
UPLOAD |
업로드 입력, 이미지/동영상/오디오 변환 실패 |
USER |
로그인 이후 사용자 계정 상태와 접근 제한 실패 |
경계 규칙:
MATCH는 이미 존재하는 match entity와 그 상호작용을 소유한다. 매칭 일정/자격/제안 제한은MATCHING이 소유한다.REVIEW는 cross-domain review status contract나 sync invariant에만 쓴다. 특정 회원 심사 기능은MEMBER_REVIEWsource가 아니라MEMBERsource의MEMBER_REVIEW_*code segment로 표현한다.AUTH_TOKEN,AUTH_LOGIN,AUTH_SIGNUP,MEMBER_AUTH_REVIEW,MEMBER_PROFILE_EDIT,MATCH_REVIEW,LOUNGE_CONTENT같은 operation namespace는 source가 아니라 code segment다.
금지 예시:
AUTH_ACCOUNTAUTH_LOGINAUTH_SIGNUPAUTH_TOKENLOUNGE_CONTENTMATCH_REVIEWMEMBER_AUTH_REVIEWMEMBER_PROFILE_EDITMEMBER_MANAGER_SELECTIONMEMBER_REVIEWREVIEW_STATUS
error_code¶
error_code는 실패 원인을 나타내는 stable wire value다.
규칙:
- 대문자 snake case를 사용한다.
${source}_로 시작한다.source뒤에 원인과 판정 기준을 드러내는 segment를 붙인다.- 세부 기능명은 source가 아니라 code segment에 둔다. 예:
source: "MEMBER",code: "MEMBER_AUTH_REVIEW_LIMIT_EXCEEDED". - product prefix, client prefix, 화면 prefix를 붙이지 않는다.
- 같은 의미의 실패 원인을 여러 code로 쪼개지 않는다.
- 서로 다른 복구 행동이나 표시 정책이 필요하면 별도 code로 분리한다.
error_surfaces¶
error_surfaces는 영향 범위 metadata다.
현재 허용 surface:
ADMIN_WEBMOBILE_APPSHARED_API
규칙:
- 응답 JSON에 노출하지 않는다.
- Mobile/Admin runtime contract와 Swagger 문서 생성에 사용한다.
- operation 소비 근거 없이 surface를 추가하지 않는다.
- 하나의 실패가 여러 surface에 영향을 주면
surfaces배열에 모두 명시한다.
error_action¶
error_action은 클라이언트 기본 처리 방식이다.
현재 허용 action:
RETRYFIX_REQUESTLOGIN_REQUIREDCONTACT_SUPPORT
규칙:
- operation별 UI copy를 action에 넣지 않는다.
- 같은 code의 기본 action은 하나다.
- operation별 예외 처리는 operation handler에서 명시한다.
Client Runtime Contract¶
Mobile/Admin은 서버 catalog에서 생성된 runtime contract를 @coupler-developer/coupler-api-contracts package로만 소비한다. package public response/envelope 타입도 이 runtime contract의 strict ErrorData를 실패 기본 타입으로 사용한다. package 발행/소비 절차는 API 클라이언트 계약 패키지 정책을 따른다.
권장 산출물:
errorContract.ts:API_ERROR_CODE,API_ERROR_DEFINITION, context pattern 상수errorMessages.ts: locale-backed client message maperrorRuntime.ts:ErrorData타입, 외부 JSON guard, message argument helper
이 산출물은 coupler-api/packages/contracts/src/generated/에서 생성한다. Package artifact는 build/publish로 배포한다. client boundary/runtime validation용이며 서버 production callsite API가 아니다.
금지한다:
- Mobile/Admin feature code에 raw string 비교를 흩뿌리는 방식
getErrorCode,getApiErrorCode,hasApiErrorCode같은 generic raw helper- legacy numeric result code 기반 신규 분기
- token fallback, dual parser, shim, 임시 wrapper 추가
- display message 문자열 기반 분기
허용한다:
api/apiError.ts같은 API boundary facade가 generated runtime의 상수, 타입, guard를 재노출하는 방식- semantic failure helper module 내부의 non-exported
hasFailureErrorCode같은 작은 구현 helper. 단, feature code에는isAuthTokenExpiredFailure,isMatchingScheduleAlreadySentFailure처럼 의미가 드러나는 함수만 노출한다. - generator/test 내부에서 generated
API_ERROR_CODE와 raw wire value를 검증하는 방식
클라이언트 표시 메시지는 generated messageKey와 messageArgContextKeys로 만든다. messageArgContextKeys가 요구하는 context 값이 없으면 %s 같은 placeholder를 그대로 노출하지 않고 surface별 fallback 처리를 해야 한다.
error_action 처리 기준:
error_action은 실패의 기본 처리 방향이다. operation handler는 먼저error_action으로 전역/공통 UX 여부를 판단하고, 필요한 경우에만error_codesemantic helper로 세부 복구 경로를 고른다.- 공통 request wrapper는 본인이 완료할 수 있는 전역 UX만 client-local handled result로 바꾼다. navigation 컨텍스트가 screen-local인 Mobile 흐름처럼 공통 wrapper가 로그인/재인증 UX를 결정적으로 완료할 수 없으면 operation/screen handler가
LOGIN_REQUIRED를 처리할 수 있다. LOGIN_REQUIRED실패를 generic message 표시만으로 끝내면 안 된다. 단, 로그인/재인증 이동 방식은 공통 boundary 또는 operation/screen handler 중 클라이언트 책임 경계가 더 명확한 곳에 둔다.
Mobile/Admin request wrapper가 전역 UX를 이미 완료한 경우에는 API ErrorData 성공/실패와 섞지 않고 client-local handled result로 분리한다.
- handled result는
type: "handled"같은 명시 discriminator를 가져야 한다. - handled result는
undefined,null, rawerror_code, legacy numeric result code로 표현하지 않는다. - handled result는 서버 실패 원인을 대체하지 않는다. 서버가 envelope를 반환한 실패는 먼저 generated runtime contract로 검증한다.
- operation handler는 handled result를 success/failure 처리 전에 종료해야 한다.
필요한 helper는 operation 의미를 드러내야 한다.
위와 같은 helper는 다음 조건을 만족할 때만 허용한다.
- 특정 operation 소비 근거가 있다.
- 내부에서 runtime contract validation을 사용한다.
- raw
error_code접근을 외부로 노출하지 않는다. - helper 이름이 클라이언트 복구 행동 또는 도메인 의미를 드러낸다.
Swagger/OpenAPI¶
Swagger/OpenAPI는 ERROR_CATALOG 기준으로 실패 응답 계약을 작성하고 검증한다. 자동 생성 산출물이 아닌 수동 YAML은 Swagger ErrorData contract test가 catalog와 대조해 drift를 차단해야 한다.
각 operation 문서에는 다음을 포함한다.
- 가능한
error_code error_actionerror_contextschema. 단, wire 응답 schema는 descriptormessageArgContextKeys에 공개하기로 한 메시지 인자만 포함한다.- operation별 표시/복구 책임
request_id추적 규칙
문서에 넣지 않는다:
- 서버 내부 stack trace
- DB schema 세부사항
- transition 상태
- client-only 임시 helper
coupler-api/packages/contracts/src/generated/apiContract.ts는 Swagger success schema를 그대로 투영한 generated artifact다. Swagger에 success schema가 없거나 느슨하면 generated success data type은 unknown 또는 loose object가 될 수 있으므로, 이 산출물을 전체 success DTO 완성 증거로 해석하지 않는다. release/cutover 판단에서 이 artifact와 publish된 contracts package version은 API 공통 응답 계약 정책의 envelope/error boundary와 documented success schema freshness 근거로 사용한다.
Transition 계층 금지¶
최종 구조와 contract cutover 완료 후보의 조건은 transition 계층 0건이다.
transition 계층으로 본다:
- bridge/adapter/shim
- dual parser
- legacy alias
- temporary wrapper
- intermediate manifest
- generic raw error-code helper
- 서버 public
ERROR_CODEalias path/group/name/codePrefix기반 taxonomy DSL
구버전 클라이언트와의 API/DB 하위 호환은
엔지니어링 가드레일의 API 계약과 runtime-state 안전성의 독립 판정을 따른다.
Additive error descriptor처럼 직전 계약을 깨지 않는 변경은 transition이 아니다. 제거 예정 bridge·alias·dual
parser가 필요하면 API cutover: Yes이며 명시한 Exit Gate 없이 최종 구조에 남기지 않는다.
변경 절차¶
API 에러 계약을 변경할 때는 다음 순서를 지킨다.
ERROR_CATALOG에 descriptor를 추가하거나 수정한다.source,surfaces,action,messageKey,messageArgContextKeys가 위 taxonomy와 맞는지 검증한다. 특히 operation/flow/화면/판정 상태가source로 들어가지 않았는지 확인한다.- 실패 응답 callsite가 descriptor를 직접 전달하는지 확인한다.
- generated Mobile/Admin runtime contract를 갱신한다.
- Swagger/OpenAPI 문서를 갱신하고 catalog 정합성 검증을 통과시킨다.
- operation별 client handler가 raw helper 없이 의미 기반으로 처리하는지 확인한다.
- lint, typecheck, docs validation을 통과시킨다.
리뷰 체크리스트¶
- [ ] 서버 실패 응답 callsite가
ErrorDescriptor를 전달하는가? - [ ] 서버 production code에 public
ERROR_CODEalias 또는 rawerror_code문자열 전달이 없는가? - [ ]
ERROR_CATALOG가 유일한 API 에러 계약 SoT인가? - [ ] descriptor field가
code,source,surfaces,action,messageKey,messageArgContextKeys로 제한되는가? - [ ]
path,group,name,codePrefix, singularerror_surface가 없는가? - [ ]
error_code가 stable wire value이고 product/client/surface prefix를 포함하지 않는가? - [ ]
error_source가 UI surface, operation, flow, 화면, 판정 상태가 아니라 상위 서버 도메인 또는 안정적인 모듈인가? - [ ]
AUTH_ACCOUNT,AUTH_LOGIN,AUTH_SIGNUP,AUTH_TOKEN,LOUNGE_CONTENT,MATCH_REVIEW,MEMBER_AUTH_REVIEW,MEMBER_PROFILE_EDIT,MEMBER_MANAGER_SELECTION,MEMBER_REVIEW,REVIEW_STATUS같은 세부 namespace가source로 승격되지 않았는가? - [ ]
error_surfaces가 응답 JSON이 아니라 영향 범위 metadata로만 쓰이는가? - [ ]
error_action이 UI copy나 operation 이름을 담지 않는가? - [ ]
error_context에 민감정보, display message, 내부 구현값이 없는가? - [ ] 계약된 JSON API 실패가 공통 응답 envelope로 반환되고, HTTP non-2xx가
ErrorDatataxonomy와 섞이지 않는가? - [ ]
messageKey가 비어 있지 않고,messageArgContextKeys가 요구하는 값이error_context에서 문자열/숫자로 보장되는가? - [ ] Mobile/Admin이 generated runtime contract로 JSON boundary를 검증하는가?
- [ ] Mobile/Admin feature code에 generic raw error-code helper가 없는가?
- [ ] Admin 목록 endpoint가 success/failure 모두 공통 envelope을 사용하는가?
- [ ] file/proxy transport 실패는
ErrorData밖에서 처리되는가? - [ ] Swagger/OpenAPI가 같은 catalog 기준으로 검증되는가?
- [ ] final structure 또는 contract cutover 완료 후보에 transition 계층이 0건인가?
관련 문서¶
content/policy/engineering-guardrails.mdcontent/policy/code-review-policy.mdcontent/policy/testing-strategy.mdcontent/flows/cross-project/api-contract-mobile-release-flow.mdcontent/technical-debt/technical-debt.md