HTTP 상태 코드 정리: 401·403부터 409·422, 재시도 기준까지
들어가며
API 가 실패한 요청에 모두 400 을 돌려준다고 해 보겠습니다. 클라이언트는 주소를 잘못 쓴 건지, 로그인이 풀린 건지, 잠깐 기다렸다 다시 보내면 되는 건지 알 수 없습니다. 결국 응답 본문의 문장을 읽어 분기하는 코드가 생깁니다.
상태 코드는 결과를 알리는 동시에 클라이언트가 다음에 할 일을 알려 주는 신호입니다. 이번 글에서는 HTTP 메서드의 의미를 짧게 짚고, 자주 헷갈리는 401·403, 400·422, 409, 5xx 와 재시도 기준을 정리합니다.
1. 메서드는 의도, 상태 코드는 결과
요청의 메서드는 무엇을 하려는지를 나타냅니다. GET 은 조회, POST 는 생성이나 처리 실행, PUT 은 자원 전체 교체, PATCH 는 일부 수정, DELETE 는 삭제입니다.
PUT 과 PATCH 는 자주 비교됩니다. PUT 은 보낸 본문으로 자원을 통째로 바꾸므로 빠진 필드는 지워질 수 있고, 같은 요청을 여러 번 보내도 결과가 같은 멱등 메서드입니다. PATCH 는 바꿀 부분만 보내며, "1 증가" 같은 내용이면 멱등이 아닙니다.
응답의 상태 코드는 그 의도가 어떻게 끝났는지를 세 자리 숫자로 알립니다. 첫 자리가 큰 갈래를 정합니다.
2. 다섯 갈래의 상태 코드
1xx 는 처리 중이라는 정보성 응답이라 API 에서 직접 다룰 일은 드뭅니다. 실제로 자주 쓰는 것은 나머지 네 갈래입니다.
상태 코드는 점원의 대답을 첫 마디로 먼저 나눈 분류표예요.
2xx 는 성공입니다. 조회는 200 OK, 생성은 201 Created 로 답하고 새 자원 주소를 Location 헤더에 담습니다. 돌려줄 본문이 없으면 204 No Content 를 씁니다.
3xx 는 클라이언트가 추가로 해야 할 일이 있다는 뜻입니다. 301 은 영구 이동, 302 는 임시 이동이고, 304 Not Modified 는 바뀐 게 없으니 가지고 있는 캐시를 쓰라는 뜻입니다.
4xx 는 클라이언트가 고쳐야 하는 문제, 5xx 는 서버 쪽 문제입니다. 같은 요청을 그대로 다시 보내서 나아질 가능성이 있는 것은 5xx 쪽입니다.
3. 401 과 403
이름 때문에 가장 많이 헷갈리는 쌍입니다. 401 Unauthorized 는 이름과 달리 인증 실패, 즉 "누구인지 모른다" 입니다. 403 Forbidden 은 누구인지는 알지만 권한이 없다는 뜻입니다.
401은 "누구세요?", 403은 "누군지는 알지만 들어오시면 안 돼요"예요.
누구인지 밝히면 풀린다. 응답에 `WWW-Authenticate` 로 인증 방식을 알린다.
다시 로그인해도 같다. 권한을 받아야 풀린다.
401 을 받은 클라이언트는 토큰을 갱신하거나 다시 로그인한 뒤 재요청하면 됩니다. 명세상 401 응답에는 어떤 방식으로 인증해야 하는지 알리는 WWW-Authenticate 헤더를 함께 보냅니다. 403 은 다시 로그인해도 결과가 같으므로 권한을 요청하거나 포기해야 합니다.
남의 자원에 403 대신 404 를 주는 서비스도 많습니다. 403 은 "그런 자원이 있긴 하다" 는 사실을 드러내기 때문에, ID 를 하나씩 대 보며 존재 여부를 캐는 열거 공격에 단서가 됩니다.
4. 4xx 를 이유별로 나누기
같은 거절이라도 이유가 다르면 클라이언트가 할 일이 달라집니다.
4xx 세분은 점원이 "안 돼요" 대신 안 되는 이유를 말해 주는 거예요.
- 깨진 JSON → 400형식 오류
- 잘못된 값 → 400입력 오류인지 모름
- 이름 중복 → 400재조회해도 되는지 모름
- 없는 ID → 400주소 문제인지 모름
- 깨진 JSON → 400형식 수정
- 잘못된 값 → 422입력값 수정
- 이름 중복 → 409상태 재조회
- 없는 ID → 404주소·ID 확인
POST /projects (JSON 문법이 깨짐)
→ 400 Bad Request
POST /projects {"name": 123}
→ 422 Unprocessable Content
POST /projects {"name": "alpha"} # 이미 있음
→ 409 Conflict
DELETE /health
→ 405 Method Not Allowed
Allow: GET
GET /projects/999
→ 404 Not Found400 은 요청 자체를 해석할 수 없을 때, 422 는 해석은 되지만 값이 규칙에 맞지 않을 때입니다. FastAPI 처럼 본문 검증 실패를 기본으로 422 로 돌려주는 프레임워크도 있습니다. 409 는 요청은 멀쩡하지만 현재 자원 상태와 충돌할 때라, 클라이언트는 상태를 다시 조회한 뒤 판단할 수 있습니다.
405 는 자원은 있지만 그 메서드를 받지 않을 때이며, Allow 헤더로 허용 메서드를 알려야 합니다. 화면 분기처럼 세밀한 처리는 상태 코드만으로는 부족하므로, 본문에 "code": "NAME_TAKEN" 같은 오류 코드를 함께 두는 편이 좋습니다.
5. 5xx 와 재시도
500 Internal Server Error 는 서버 코드의 예외처럼 예상하지 못한 실패입니다. 503 Service Unavailable 은 과부하나 점검으로 잠시 처리할 수 없다는 뜻이고, 429 Too Many Requests 는 요청 한도를 넘었다는 뜻입니다.
자동 재시도는 GET·PUT·DELETE 같은 멱등 메서드에, 503·429 처럼 일시적인 응답일 때만 겁니다. 서버가 Retry-After 헤더를 주면 그 시간만큼 기다립니다. POST 를 그대로 재시도하면 같은 자원이 두 번 생길 수 있으므로 멱등 키 같은 장치가 먼저 있어야 합니다.
한눈에 비교
| 코드 | 뜻 | 클라이언트의 다음 행동 |
|---|---|---|
| 400 | 요청을 해석할 수 없음 | 요청 형식 수정 |
| 401 | 인증 실패 | 토큰 갱신·재로그인 후 재요청 |
| 403 | 권한 없음 | 권한 요청, 재시도 의미 없음 |
| 404 | 자원 없음 | 주소·ID 확인 |
| 405 | 메서드 불허 | Allow 헤더의 메서드로 변경 |
| 409 | 현재 상태와 충돌 | 상태 재조회 후 판단 |
| 422 | 내용 검증 실패 | 입력값 수정 |
| 429·503 | 일시적 거절 | Retry-After 만큼 기다린 뒤 재시도 |
헷갈리기 쉬운 점
- 401 을 "권한 없음" 으로 읽는 실수. 401 은 인증 실패이고, 권한 없음은 403 입니다.
- 오류는 400 이나 500 하나로 충분하다는 오해. 409 를 400 으로 주면 클라이언트는 상태를 확인하고 다시 시도해도 되는지 알 수 없습니다.
- 실패한 요청은 무조건 재시도해도 된다는 오해. 4xx 는 같은 요청을 다시 보내도 결과가 같고, POST 는 중복 생성될 수 있습니다.
면접에서 자주 받는 질문
400 과 422 는 어떻게 가르나요?
400 은 JSON 문법이 깨진 것처럼 요청 자체를 해석할 수 없을 때입니다. 422 는 해석은 되지만 문자열 자리에 숫자가 오는 등 값이 규칙에 맞지 않을 때입니다. 둘을 나누면 클라이언트가 "형식 오류" 와 "입력값 오류" 를 다르게 안내할 수 있습니다.
권한 없는 자원에 403 대신 404 를 주는 이유는요?
403 은 자원이 존재한다는 사실을 드러냅니다. ID 를 바꿔 가며 요청하면 어떤 자원이 있는지 알아낼 수 있으므로, 남의 자원은 없는 것처럼 404 로 답해 존재 여부를 숨깁니다.
301, 302, 304 는 각각 언제 쓰나요?
301 은 주소가 영구히 바뀌었을 때라 브라우저와 검색 엔진이 새 주소를 기억합니다. 302 는 임시 이동이라 다음에도 원래 주소로 요청합니다. 304 는 조건부 요청에서 자원이 바뀌지 않았으니 캐시를 쓰라는 응답이며 본문이 없습니다.
정리
- 메서드는 요청의 의도, 상태 코드는 결과이며 첫 자리가 성공·추가 조치·클라이언트 문제·서버 문제를 가릅니다.
- 401 은 인증 실패, 403 은 권한 없음이고, 4xx 는 이유별로 나눠야 클라이언트가 다음 행동을 정할 수 있습니다.
- 자동 재시도는 멱등 메서드와 503·429 같은 일시적 응답에만 걸고
Retry-After를 지킵니다.
4는 내 탓, 5는 서버 탓이라는 큰 갈래를 먼저 잡고, 그 안에서 코드가 알려 주는 다음 행동을 읽으면 됩니다.