401과 403
401Unauthorized— 클라이언트가 미인증 상태입니다. 자격 정보가 없거나 잘못되었습니다. "Unauthorized"라는 명칭은 오해를 부르기 쉽고, 실제 의미는 "미인증"입니다.403Forbidden— 클라이언트는 인증되었지만 권한이 없습니다. 재인증해도 해결되지 않습니다.
모든 처리는 브라우저 내부에서 실행됩니다 — 파일·입력은 서버로 전송되지 않습니다.
HTTP는 첫 자리 숫자로 응답 코드를 5개 클래스로 묶습니다. 구분이 성겨서 "계속 진행하라"부터 "폰트가 다 없다"까지가 한 클래스에 들어가지만, 첫 한 자리만으로 요청이 성공했는지, 무엇이 잘못 되었는지, 원인이 어느 쪽인지를 알 수 있습니다.
| 클래스 | 범위 | 명칭 | 실제 의미 |
|---|---|---|---|
| 1xx | 100–199 | 정보 | 중간 응답. 요청을 받아 처리 중이며 최종 응답은 별도로 전송됨. |
| 2xx | 200–299 | 성공 | 요청 성공. 의미는 메서드에 따라 달라짐. GET이면 200, 리소스 생성이면 201, "처리 완료, 본문 없음"이면 204. |
| 3xx | 300–399 | 리디렉션 | 클라이언트 측에 추가 동작이 필요. 대부분 다른 URL로의 이동, 또는 캐시가 여전히 유효하다는 통지. |
| 4xx | 400–499 | 클라이언트 오류 | 클라이언트 측의 잘못. 구문 오류, 인증 누락, 존재하지 않는 리소스 요청, 받아들일 수 없는 데이터 전송 등. |
| 5xx | 500–599 | 서버 오류 | 서버 측에서 정상적인 요청을 처리하지 못함. 원인은 서버 측에 있으며 클라이언트에는 잘못이 없음. |
실무 API 버그의 상당수는 이름이 비슷해 보이는 몇 짝의 코드를 헷갈려 발생합니다. 구분은 의도적으로 두어진 것이며, 올바른 선택이 클라이언트에게 다음 동작을 알려 줍니다.
401Unauthorized— 클라이언트가 미인증 상태입니다. 자격 정보가 없거나 잘못되었습니다. "Unauthorized"라는 명칭은 오해를 부르기 쉽고, 실제 의미는 "미인증"입니다.403Forbidden— 클라이언트는 인증되었지만 권한이 없습니다. 재인증해도 해결되지 않습니다.301Moved Permanently— 리소스가 영구적으로 다른 URL로 이동했습니다. 브라우저는 리디렉션 시 메서드를 GET으로 바꾸는 경우가 있습니다(역사적 동작).302Found— 일시적 리디렉션이며 301과 같은 메서드 변경 역사적 동작을 가집니다. 303이나 307의 의도로 잘못 쓰이기 쉽습니다.307Temporary Redirect— 302와 비슷하지만 메서드를 유지해야 합니다. POST는 리디렉션 후에도 POST 그대로입니다.308Permanent Redirect— 301과 비슷하지만 메서드를 유지해야 합니다. 사실상 현대판 301입니다.200OK— 본문이 있는 범용 성공입니다. GET의 기본값입니다.201Created— 새 리소스가 생성되었습니다. 생성된 위치를 가리키는 Location 헤더를 함께 보냅니다.202Accepted— 요청을 큐에 넣었습니다. 처리는 아직 시작되지 않았고 비동기로 완료됩니다.204No Content— 성공이지만 본문을 의도적으로 비웁니다. 성공한 DELETE의 기본값입니다.400Bad Request— 요청 자체가 잘못되었습니다. JSON 구문 오류, 필수 헤더 누락 등 서버가 파싱하지 못한 경우입니다.422Unprocessable Content— 요청은 이해되었지만 데이터가 의미적으로 잘못된 경우입니다. 예: 이메일 필드에 @가 없거나, 수량이 음수.502Bad Gateway— 프록시가 상류 서버에서 잘못된 응답을 받았습니다. 상류가 프록시가 쓸 수 없는 응답을 보낸 상태입니다.503Service Unavailable— 서버 자체가 일시적으로 다운된 상태입니다. 과부하나 점검 등이며 Retry-After 헤더가 함께 오는 경우가 많습니다.504Gateway Timeout— 프록시가 상류 응답을 너무 오래 기다리다 포기했습니다. 502가 "잘못된 응답"이라면 504는 "응답 없음"입니다.REST API에서의 관행적 대응은 짧게 정리됩니다. GET 성공: 200 (본문 포함) 또는 304(캐시가 유효). 새 리소스를 만드는 POST 성공: 201과 Location 헤더. PUT 또는 PATCH 성공: 200(갱신된 본문 포함) 또는 204(본문 없음). DELETE 성공: 204. 검증 실패: 422. 인증 부족: 401. 권한 부족: 403. 리소스 미발견: 404. 서버 측 버그: 500.
자주 보이는 안티패턴 두 가지를 짚어 둡니다. 첫째, 본문에 에러 메시지를 담아 200을 돌려주는 형식(status: 200, error: 'not found')입니다. 모니터링 도구는 모두 상태 코드를 기준으로 판정하므로 이 형식은 그 전제를 통째로 무력화합니다. 둘째, 모든 실패에 500을 돌려주는 패턴입니다. 클라이언트 측에 "재시도 가능 (503)", "입력 오류(400)", "지원 문의 필요(500)"를 구분할 수단이 사라집니다.
현행 권위 있는 참조 문서는 RFC 9110(HTTP Semantics, 2022년 6월)이며, 과거 12개 RFC를 한 문서로 통합하면서 RFC 7231·7232를 명시적으로 폐기했습니다. 일상적으로 쓰는 코드의 대부분은 이미 RFC 1945(HTTP/1.0, 1996년)에 포함되어 있었습니다. 308 Permanent Redirect는 RFC 7538, 421 Misdirected Request는 RFC 7540, 451 Unavailable For Legal Reasons는 RFC 7725에서 추가되었습니다.
실무 조회에는 IANA의 HTTP Status Code Registry가 현행 소스입니다. 할당된 모든 코드와 정의 RFC를 일람으로 제공합니다. 감사나 규격 적합 보고에 정식 표현이 필요하면 RFC 9110과 레지스트리를 인용하세요. 422가 무엇이었는지만 떠올리면 된다면 위 검색창이 훨씬 빠릅니다.
검색창에 코드·이름·사용 사례 키워드(`404`·`not found`·`redirect`·`unauthorized`·`idempotent` 등)를 입력하면 테이블이 실시간으로 필터됩니다. 클래스 칩(1xx / 2xx / 3xx / 4xx / 5xx)을 클릭하면 더 좁혀집니다. 각 행은 정규명, 한 줄 설명, "언제 쓰는가" 힌트, 그것을 정의하는 IETF 섹션 링크를 갖습니다. 중심은 `RFC 9110`(오랜 RFC 7231 / 7232 / 7233 / 7234 / 7235 시리즈를 2022년에 대체한 개정판)입니다.
API 설계, 서버 로그 읽기, 낯선 코드가 나오는 요청 디버깅에 사용하세요. 주목받기 쉬운 건 4xx·5xx 클래스지만, 3xx 리다이렉트 코드와 2xx 성공 코드에도 실질적 의미 무게가 있습니다. `301`과 `308`, `200`과 `204`와 `201`, `409`와 `422`는 캐시와 클라이언트에 서로 다른 동작을 알립니다. 적절한 코드를 고르면 라이브러리(axios·RestSharp·Spring)와 인프라(CloudFront·nginx·AWS API Gateway)가 자체 훅 없이도 예측 가능한 동작을 합니다.
search: 422
422 Unprocessable Content (RFC 9110 §15.5.21) Use: request is syntactically valid but the server cannot process it for semantic reasons — failed validation, business rule violation. Often confused with 400.
`400 Bad Request`은 요청 자체가 망가졌다는 뜻(JSON 손상, 필수 헤더 누락 등)이고, `422`는 요청 의미는 이해되지만 내용이 잘못됐다는 뜻(이미 사용 중인 email, age는 18 이상이어야 함 같은 것)입니다. Stripe·GitHub·Twilio 같은 현대 API는 검증 에러에 `422`를 자주 택해 백엔드 로그에서 "클라이언트가 쓰레기를 보냈다"와 "클라이언트가 허용되지 않는 시도를 했다"를 분리합니다.
search: redirect permanent
301 Moved Permanently — historic permanent redirect; browsers may rewrite the method to GET on POST / PUT. 308 Permanent Redirect (RFC 7538) — same semantics but preserves the original method and body.
API 엔드포인트를 신설할 때 리다이렉트 후에도 클라이언트가 POST·PUT을 유지하도록 하려면 `308`을 쓰세요. SEO나 옛 URL → 새 URL 이전에서 GET으로 다시 쓰는 것이 허용되고(역사적으로 기대되는) 경우에는 `301`을 씁니다. 같은 구분이 임시 리다이렉트에도 있습니다 — `302`(레거시, GET으로 다시 쓸 수 있음) vs `307`(메서드 유지).
search: unauthorized forbidden
401 Unauthorized — request lacks valid authentication. Server must return a WWW-Authenticate header. Despite the name, this is really "Unauthenticated". 403 Forbidden — request is authenticated but the principal is not allowed to perform this action. No WWW-Authenticate; retrying with different credentials won't help unless permissions change.
이름이 헷갈리지만 `401`은 인증(당신은 누구?)에 관한 것이고 `403`은 인가(무엇을 할 수 있는가?)에 관한 것입니다. 흔한 패턴은 인증 토큰이 없거나 만료된 경우 `401`(클라이언트는 재로그인해야 함), 토큰은 유효하지만 스코프가 부족한 경우 `403`(클라이언트는 권한 상승을 요청해야 함)을 반환하는 것입니다. 일부 API는 미인가 호출자에게 리소스 존재를 누설하지 않기 위해 `403` 대신 `404`를 반환하기도 합니다.
코드는 IANA의 HTTP Status Code Registry에서 가져옵니다. 그것이 공식 권위입니다. 현재 핵심 정의는 RFC 9110(HTTP Semantics, 2022년 6월)에 있고, RFC 9111(Caching), 7538(308 리다이렉트), 6585(precondition required·too many requests), 8297(early hints) 같은 주제별 RFC가 확장을 담당합니다. 각 행은 정의 섹션으로 링크를 두어 명세로 검증할 수 있습니다.
레지스트리는 비교적 관대해서 IETF 절차를 거치면 누구나 코드 할당을 요청할 수 있습니다. 흔한 것(`200`·`404`·`500`)도 있고 니치한 것(만우절 RFC의 `418 I'm a teapot`, 『화씨 451도』에서 따온 `451 Unavailable For Legal Reasons`)도 있습니다. 좁은 프로토콜용도 있어 WebDAV는 RFC 4918에서 207 / 422 / 423 / 424 / 507을 추가했습니다. 이 레퍼런스는 모두 표시하므로 CDN 로그나 서드파티 API에 등장해도 식별할 수 있습니다.
불법은 아니지만 오해를 부릅니다. HTTP 상태 코드는 *인프라 계층*(로드 밸런서·재시도 라이브러리·캐시 계층·모니터링 대시보드)과 대화하는 것입니다. 논리적으로는 실패인데 `200`을 반환하면 CDN은 그 에러 응답을 캐시하고, 재시도 라이브러리는 호출을 성공으로 간주하며, 온콜 대시보드는 녹색 점으로 표시해 버립니다. `2xx`는 실제 성공에만 사용하고, 클라이언트 에러에는 `4xx`, 서버 에러에는 `5xx`를 쓰세요. 주변 생태계가 올바르게 다룹니다. 주요 예외는 GraphQL로, 그 명세는 프로토콜 계층이 HTTP에 의존하지 않으므로 에러를 본문에 포함시켜 `200`으로 반환합니다.
둘은 유용합니다. `100 Continue`는 HTTP/1.1 클라이언트가 `Expect: 100-continue`를 붙여 큰 바디를 보내기 전에 서버에 수신 가능 여부를 묻는 데 쓰이며, 대부분의 서버가 투명하게 처리합니다. `103 Early Hints`(RFC 8297, 2017년)는 서버가 실제 `200` 응답이 준비되기 전에 CSS·JS 프리로드 힌트를 보낼 수 있게 합니다 — Cloudflare와 Fastly가 지원합니다. 나머지(WebSocket 업그레이드용 `101 Switching Protocols`, WebDAV용 `102 Processing`)는 프로토콜 고유입니다. 일상적인 API 개발에서 1xx를 보는 일은 드뭅니다.
셋 다 상류·게이트웨이 계열의 실패지만 보는 각도가 다릅니다. **`502 Bad Gateway`**는 프록시가 상류로부터 *깨진* 응답(커넥션 리셋, 잘못된 HTTP)을 받았다는 뜻입니다. **`503 Service Unavailable`**은 서버 자체가 요청을 처리할 수 없는 상태(과부하·점검·우아한 종료)이며 `Retry-After` 헤더를 함께 보내야 합니다. **`504 Gateway Timeout`**은 프록시가 상류 응답을 기다리다 타임아웃됐다는 뜻입니다. nginx → 앱 구성이라면 `502`는 "앱 크래시", `503`은 "앱이 '꽉 찼다'고 답함", `504`는 "앱 응답이 너무 느림"입니다. 적절한 코드는 온콜이 재시작·스케일 아웃·지연 조사 중 무엇을 고를지의 판단 재료가 됩니다.
엄격하게는 불가합니다. IANA 레지스트리가 정본이고 프록시·브라우저·미들웨어는 미등록 코드를 그 클래스의 기본 동작으로 다룰 수 있습니다(4xx는 무엇이든 400처럼, 5xx는 무엇이든 500처럼). 실무에서는 프레임워크가 용인하는 경우도 많고 앞 자리만 의미 있으면 동작합니다. 사설 API에서는 `419 Authentication Timeout`(Laravel)이나 `420 Method Failure`(Twitter 옛 API)처럼 미등록 코드가 사용된 예도 있습니다. 새 공개 API에서는 등록된 코드에 맞추고 세부는 응답 본문에 두세요. 클라이언트가 미등록 코드를 대체로 동일하게 무시하므로 추가 정보 가치가 작습니다.
HTTP 상태 코드는 서버가 응답과 함께 반환하는 3자리 정수입니다. 앞 자리가 **클래스**를 식별합니다. `1xx` 정보(드물고 프로토콜 고유), `2xx` 성공, `3xx` 리다이렉션, `4xx` 클라이언트 에러, `5xx` 서버 에러. RFC 9110(2022년 6월)은 그 전에 RFC 7231 / 7232 / 7233 / 7234 / 7235로 흩어져 있던 정의를 한 문서로 통합한 현행 규범 레퍼런스입니다. IANA의 HTTP Status Code Registry가 할당된 코드의 권위 있는 목록을 보관합니다.
인접 코드 간 의미 차이는 사람이 상상하는 것 이상으로 중요합니다. `200`과 `204`는 둘 다 "성공"이지만 `204`는 바디를 갖지 않습니다. 클라이언트는 파싱하지 말아야 합니다. `201`은 리소스 생성을 확인하고 응답에는 새 리소스의 표현이나 `Location` 헤더가 포함됩니다. `301`과 `308`은 둘 다 "영구 리다이렉트"이지만 `308`은 HTTP 메서드를 유지하고, `301`은 역사적으로 POST를 GET으로 다시 씁니다. `409 Conflict`와 `422 Unprocessable Content`는 둘 다 "쓰기를 거부했다"이지만, `409`는 상태 충돌(동시 편집·중복 키), `422`는 검증 실패(잘못된 email 형식)용입니다. 적절한 코드를 고르는 것은 시그널링이며 현학이 아닙니다. 캐시·재시도 라이브러리·관측 도구가 본문을 읽지 않고 코드만 보고 반응합니다.
일상의 API 설계를 형성하는 인접 개념이 3가지 있습니다. **멱등성**은 메서드를 분류하고(`GET`·`PUT`·`DELETE`는 멱등, `POST`는 비멱등) 안전한 재시도 동작을 결정합니다. 상태 코드 설계는 이와 맞물려 클라이언트가 `503`에서 재시도할지를 판단할 수 있게 합니다. **캐싱**(RFC 9111)은 어떤 응답이 저장될지를 결정합니다. `200`·`203`·`204`·`300`·`301`·`404`·`405`·`410`·`414`·`501`은 기본으로 캐시 가능하며, 다른 것은 명시 헤더가 필요합니다. **콘텐츠 협상**은 `Accept`·`Accept-Language` 같은 헤더를 사용하며 제시 후보 중 어느 것도 맞지 않으면 `406 Not Acceptable`을 반환합니다. 상태 코드는 이 셋의 한가운데에 위치하므로 잘 고르는 것이 스택 전체에 이득이 됩니다.
curl 명령을 붙여 넣으면 fetch / axios / Go net/http / Python requests에 해당하는 코드를 생성합니다. `-X` / `-H` / `-d` / `-u` / `--form` / 백슬래시 줄바꿈을 지원합니다.
JSON을 정렬하고 검증합니다. 들여쓰기 출력, 압축 출력, 명확한 에러 위치 표시를 지원합니다.
텍스트와 Base64를 서로 변환합니다. UTF-8을 지원합니다.
URL 퍼센트 인코딩/디코딩.
요청 초기 부분 수신. 나머지를 보내 계속 진행해도 됨.
Upgrade 헤더에 따라 프로토콜 전환(HTTP → WebSocket 등).
WebDAV: 처리 중, 아직 응답 없음.
최종 응답 준비 중 미리 가져올 리소스를 힌트로 전달.
요청 성공. 의미는 메서드에 따라 다름.
새 리소스 생성됨. 보통 POST 응답.
요청 수락됨, 아직 처리 안 됨 (비동기 작업).
변환 프록시가 응답을 수정함.
성공했으나 본문 없음. DELETE 또는 멱등 업데이트.
클라이언트에 문서 뷰 리셋(예: 폼 초기화) 지시.
Range 헤더에 따른 부분 응답. 재시작 가능 다운로드·스트리밍에 사용.
WebDAV: 본문에 여러 상태 코드(XML).
WebDAV: 이전 응답에서 이미 열거된 멤버.
Delta encoding: instance-manipulation으로 응답.
여러 표현 존재. 클라이언트가 선택.
영구 이동. 북마크·링크 갱신 필요.
임시 리다이렉트. 실제로 메서드가 변경되는 구현 다수.
다른 URL을 GET하라는 지시. POST/Redirect/GET 패턴.
캐시 응답 유효. 본문 없음.
302와 같으나 메서드 유지.
301과 같으나 메서드 유지.
구문 오류·프레이밍 오류 등 서버가 이해할 수 없는 요청.
인증 필요 또는 실패. WWW-Authenticate 헤더 참조.
유료 리소스 예약. 실제로는 거의 안 씀.
인증되었으나 권한 없음.
해당 URL에 리소스 없음. 가장 흔한 오류.
이 리소스에서 지원하지 않는 메서드. Allow 헤더 참조.
Accept-* 헤더에 맞는 응답을 만들 수 없음.
401의 프록시 버전.
요청이 너무 느려 서버가 연결 종료.
현재 상태와 충돌(예: 동시 편집).
영구 삭제됨. 전달 URL 없음.
Content-Length 헤더 필수.
If-Match / If-Unmodified-Since 조건 불일치.
요청 본문이 서버 상한 초과.
URL이 서버 허용치보다 김.
엔드포인트가 지원하지 않는 Content-Type.
요청한 Range가 범위 밖(EOF 초과 등).
Expect 헤더 요구를 만족할 수 없음.
만우절 농담(RFC 2324). 일부 사이트가 불가능 요청에 반환.
해당 authority에 응답할 수 없는 서버로 요청 도달.
구문은 OK이나 의미상 무효. 폼 검증에서 자주 사용.
WebDAV: 리소스 잠김.
WebDAV: 선행 요청 실패.
재전송 가능성이 있어 처리 거부.
프로토콜 변경 필요.
조건부 요청 필요. lost-update 방지.
속도 제한. Retry-After 헤더 참조.
요청 헤더 합계가 상한 초과.
법적 요구로 차단. 명칭은 화씨 451도에서 유래.
서버 측 일반 오류. 버그 또는 미처리 예외.
메서드 미구현.
프록시가 상류 서버에서 잘못된 응답 수신.
과부하 또는 점검 중. 잠시 후 재시도.
상류 서버 응답 전 타임아웃.
요청의 HTTP 버전 미지원.
컨텐츠 협상 루프 발생.
WebDAV: 저장 공간 부족.
WebDAV: 처리 중 무한 루프 감지.
요청 처리에 추가 확장 필요.
네트워크 인증 필요(캡티브 포털).