Foundry
API 설계
기초
핵심

HTTP 상태 코드와 에러 응답 설계

200에 실패를 담으면 아무도 장애를 모른다

HTTP 상태 코드와 에러 응답 설계

코드 선택 기준

코드언제 쓰나흔한 오용
200, 201, 204성공, 생성, 본문 없음실패인데 200 + 본문 에러
400형식과 문법 오류검증 실패를 전부 400으로
401, 403미인증과 권한 없음둘을 뒤섞음
404, 409없음과 상태 충돌중복 생성에 400
422형식은 맞지만 처리 불가400과 혼용
429요청 한도 초과503으로 대체
500, 503서버 결함과 일시 과부하클라이언트 잘못을 500으로

에러 본문 표준화

{
  "type": "/errors/insufficient-balance",
  "title": "잔액이 부족합니다",
  "status": 409,
  "detail": "요청 12000원, 잔액 3500원"
}

기계가 분기할 값(type)과 사람이 읽을 문장(title, detail)을 분리한다. 필드 검증 오류는 어느 필드가 왜 틀렸는지 배열로 함께 준다.

실무 포인트

  • 200 고정 + 본문 에러코드 방식은 프록시와 모니터링이 전부 성공으로 집계해 장애 감지를 늦춘다
  • 5xx는 재시도 대상, 4xx는 재시도 무의미하다. 상태 코드가 클라이언트 재시도 정책의 입력값이다
  • 권한 없는 리소스의 존재 여부를 숨기려면 403 대신 404를 주는 선택도 있다
  • 내부 예외 메시지와 스택트레이스를 응답에 넣지 않는다
면접에서 이렇게 나옵니다

Q.모든 응답을 200으로 주고 본문에 에러 코드를 담는 API의 문제는 무엇인가요?

HTTP 를 이해하는 모든 중간 계층이 성공으로 판단하기 때문입니다.

영향받는 것결과
로드밸런서죽은 서버를 정상으로 보고 계속 요청을 보낸다
모니터링오류율이 항상 0이라 장애를 감지하지 못한다
CDN 과 캐시오류 응답을 캐시해서 계속 내보낸다
클라이언트 라이브러리예외를 던지지 않아 오류 처리가 누락된다
재시도재시도해야 할지 판단할 근거가 없다

두 번째가 특히 위험합니다. 대시보드의 오류율이 0인데 사용자는 아무것도 못 하는 상태가 됩니다. 문제를 알려면 응답 본문을 파싱하는 별도 계측을 만들어야 하고, 그것은 이미 있는 표준을 다시 만드는 일입니다.

세 번째도 실제 사고를 만듭니다. 오류 응답이 캐시되면 원인이 사라진 뒤에도 계속 오류가 나갑니다.

이 방식을 택하는 이유는 대개 클라이언트 처리를 단순하게 하려는 것인데, 그러면 오류 처리가 없는 클라이언트가 만들어지기 쉽습니다.

흔한 실수: 상태 코드와 본문 코드를 양자택일로 보는 것. 함께 씁니다. 상태 코드로 종류를 알리고, 본문에 세부 코드와 메시지를 담습니다.

Q.400과 422, 401과 403을 각각 어떤 기준으로 구분하나요?

코드기준
400요청 자체를 해석할 수 없다. JSON 형식 오류, 타입 불일치
422형식은 맞지만 값이 규칙에 어긋난다. 이메일 형식, 필수 값 누락
401신원을 모른다. 토큰이 없거나 잘못됐거나 만료됐다
403신원은 알지만 권한이 없다

앞의 둘은 파싱 단계에서 실패했는지, 검증 단계에서 실패했는지로 갈립니다. 구분하는 값은 클라이언트가 대응을 달리할 수 있다는 것입니다. 400 은 코드 버그이고, 422 는 사용자에게 입력을 고쳐 달라고 안내할 수 있습니다.

다만 422 를 안 쓰고 400 으로 통일하는 팀도 많습니다. 어느 쪽이든 팀 안에서 일관되면 됩니다.

뒤의 둘은 실무에서 자주 틀립니다.

상황코드
토큰 만료401. 갱신하면 되므로 클라이언트가 재발급을 시도할 수 있다
권한 부족403. 재로그인해도 달라지지 않는다

이 구분이 실제 동작을 바꿉니다. 401 을 받으면 클라이언트가 토큰을 갱신하고 재시도하고, 403 이면 하지 않습니다. 권한 부족에 401 을 주면 갱신과 재시도를 무한히 반복합니다.

흔한 실수: 403 을 주어 자원의 존재를 노출하는 것. 민감한 자원은 404 로 존재 자체를 숨기는 편이 안전합니다.

Q.클라이언트가 자동 재시도를 판단하려면 서버가 무엇을 제공해야 하나요?

다시 시도해도 되는지, 언제 하면 되는지를 서버가 알려줘야 합니다.

제공할 것내용
적절한 상태 코드503 과 429 는 재시도 가능, 400 은 아니다
Retry-After 헤더몇 초 뒤에 오라고 알려준다
멱등성 보장재시도해도 중복되지 않게 멱등 키를 받는다
오류 코드 구분본문에 일시적인지 영구적인지 분류를 담는다
요청 식별자같은 요청인지 서버가 판별할 수 있게

상태 코드만으로는 부족한 경우가 있습니다. 500 은 일시적일 수도, 코드 버그일 수도 있습니다. 그래서 본문에 세부 분류를 담아 주면 클라이언트가 판단할 수 있습니다.

코드재시도
408, 429, 503, 504한다
500, 502조건부로. 멱등한 요청만
400, 401, 403, 404, 422하지 않는다
409상황에 따라. 충돌 원인을 봐야 한다

두 번째 줄이 판단이 필요한 자리입니다. POST 로 결제를 만들다 500 을 받으면 실제로 처리됐는지 알 수 없습니다. 멱등 키가 없으면 재시도는 이중 결제 위험이고, 안 하면 누락 위험입니다. 그래서 서버가 멱등 키를 받아 주는 것이 재시도의 전제 조건입니다.

흔한 실수: Retry-After 를 무시하고 고정 간격으로 재시도하는 것. 서버가 알려준 시간보다 빨리 오면 상대를 더 밀어붙입니다.

Q.검증 오류가 여러 필드에서 났을 때 응답을 어떻게 설계하겠습니까?

전부 모아 한 번에 돌려줍니다. 첫 오류에서 멈추면 사용자가 여러 번 왕복해야 합니다.

응답 구조는 이런 형태가 됩니다.

항목내용
최상위오류 종류와 요청 식별자
errors 배열필드별 오류 목록
각 항목필드 경로, 오류 코드, 사람이 읽을 메시지
필드담는 것
field중첩까지 표현하는 경로items[0].quantity
code기계가 판단할 코드TOO_SMALL
message화면에 보일 문구수량은 1 이상이어야 합니다

code 와 message 를 나누는 이유가 있습니다. 클라이언트가 문구를 자기 언어로 바꾸거나 필드별 표시 위치를 정하려면 기계가 읽을 코드가 필요합니다. 메시지만 주면 문자열 비교로 분기하게 됩니다.

필드 경로를 정확히 주는 것도 중요합니다. 배열 안 몇 번째 항목의 어느 필드인지 알려주지 않으면 화면에서 어디에 오류를 표시할지 알 수 없습니다.

흔한 실수: 검증 실패에 첫 오류만 담는 것. 사용자가 다섯 번 저장을 눌러야 다섯 개를 다 고칩니다. 서버 부하보다 왕복 횟수가 사용자 경험에 훨씬 크게 작용합니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

읽었으면 문제로 확인해보세요

API 설계 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.