"첫 자리만 읽어도 응답의 큰 방향은 알 수 있어. 하지만 201과 204, 301과 308, 401과 403, 400과 422를 구분하는 나머지 디테일이 그럭저럭 작동하는 API와 믿고 쓸 수 있는 API를 갈라."
2xx — 성공을 설명하는 다섯 가지 방식
2xx는 요청이 성공 계열로 처리됐음을 나타내. 다만 202처럼 처리가 아직 끝나지 않은 경우도 있으므로, 구체적인 코드가 어떤 방식으로 성공했는지까지 설명해 줘.
- 200 OK — 가장 일반적인 성공 응답이야. 보통 응답 본문에 결과가 들어 있어. GET, 변경된 상태를 반환하는 수정 요청, 그 밖의 일반적인 성공에 사용해.
- 201 Created — 요청의 결과로 새 리소스가 생성됐다는 뜻이야. 새 리소스의 URI를
Location헤더로 알려주면 클라이언트가 후속 요청을 만들기 쉽고, 본문에 생성된 표현을 함께 보내기도 해. POST로 생성하거나, 존재하지 않던 대상 URI에 PUT으로 리소스를 만들었을 때 사용할 수 있어. - 202 Accepted — 요청을 접수했지만 처리는 아직 끝나지 않았다는 뜻이야. 본문이나
Location헤더로 작업 상태를 조회할 URI를 제공하면 클라이언트가 완료 여부를 확인할 수 있어. 정산, 영상 변환, Council 마무리처럼 오래 걸리는 작업에 잘 맞아. - 204 No Content — 요청은 성공했지만 응답 본문을 보내지 않는다는 뜻이야. 삭제 뒤나, 변경된 리소스를 되돌려주지 않는 PUT·PATCH 뒤에 자주 사용해. 모든 성공 응답에 무조건
response.json()부터 호출하는 클라이언트는 여기서 실패해. - 206 Partial Content — 범위 요청에 대한 성공 응답이야. 영상 스트리밍, 중단된 다운로드 이어받기, 대용량 파일 전송에 쓰이며 본문에는 전체 리소스가 아니라 요청한 바이트 범위만 들어 있어.
3xx — 리디렉션 뒤에도 메서드를 유지할 것인가
리디렉션에서 흔히 생기는 버그가 있어. POST를 보냈는데 새 URI를 따라간 다음 요청이 GET으로 바뀌는 경우야. 301과 302는 역사적인 호환성 때문에 일부 사용자 에이전트가 POST를 GET으로 바꿀 수 있어. 원래 메서드와 본문을 반드시 유지해야 한다면 307이나 308을 선택해야 해.
- 301 Moved Permanently — 대상 리소스의 URI가 영구적으로 바뀌었다는 뜻이야. 클라이언트는 Location에 제시된 URI를 앞으로 사용할 수 있어. 다만 역사적 동작 때문에 POST가 GET으로 바뀔 가능성을 고려해야 해.
- 308 Permanent Redirect — 301과 같은 영구 리디렉션이지만 메서드와 본문을 보존해. POST는 POST로 유지되므로 GET이 아닌 엔드포인트를 영구 이전할 때 적합해.
- 302 Found — 임시 리디렉션이야. 역사적으로 일부 클라이언트가 후속 요청의 메서드를 GET으로 바꿀 수 있어.
- 307 Temporary Redirect — 임시 리디렉션이면서 메서드와 본문을 보존해. GET이 아닌 엔드포인트를 잠시 다른 URI로 보낼 때 사용해.
- 304 Not Modified — 캐시한 표현을 계속 사용할 수 있다는 뜻이야. 조건부 요청에 대한 응답이며 본문을 보내지 않아. 자세한 내용은 이 트랙의 네 번째 레슨에서 다뤄.
4xx — 자주 혼동하는 코드들
비슷해 보이지만 계약은 분명히 다른 대표적인 경우를 짚어 보자.
401 Unauthorized와 403 Forbidden. 401은 유효한 인증 정보가 필요하다는 뜻이고, 403은 서버가 요청 주체를 알고 있어도 해당 작업을 허용하지 않는다는 뜻이야. 인증되지 않은 요청에 무조건 403을 주면 리소스 존재 여부를 불필요하게 드러낼 수 있고, 인증에는 성공했는데 401을 주면 클라이언트가 잘못된 복구 절차를 밟게 돼. 401 응답에는 WWW-Authenticate 헤더로 적용 가능한 인증 방식을 제시해야 해.
400 Bad Request와 422 Unprocessable Content. 400은 잘못된 JSON처럼 서버가 요청을 올바르게 해석할 수 없거나 요청 형식이 유효하지 않을 때 사용해. 422는 문법과 형식은 이해했지만 내용이 검증 규칙을 만족하지 못할 때 적합해. 예를 들어 이메일 필드에 "not-an-email"이 들어 있거나 수량이 음수인 경우야. 둘을 모두 400으로 뭉뚱그리면 클라이언트가 오류를 진단하고 수정할 단서를 잃어.
404 Not Found와 410 Gone. 404는 현재 리소스를 찾을 수 있다는 정보가 없다는 뜻이야. 과거에 존재했는지, 나중에 다시 생길지는 말하지 않아. 410은 리소스가 존재했지만 의도적으로 제거됐고 영구적으로 사라졌음을 알릴 때 사용해. 클라이언트는 410을 받으면 해당 URI를 인덱스에서 제거하는 판단을 내릴 수 있어.
409 Conflict. 리소스의 현재 상태와 요청이 충돌해 적용할 수 없을 때 사용해. 활성 종속 리소스 때문에 삭제할 수 없거나 고유성 제약을 위반하는 생성 요청이 대표적이야. If-Match 같은 조건부 헤더가 실패한 경우에는 더 구체적인 412 Precondition Failed를 사용해야 해.
5xx — 서버 측 실패, 재시도는 조건부
- 500 Internal Server Error — 서버가 예상하지 못한 오류를 만났다는 뜻이야. 멱등한 요청이거나 중복 방지 장치가 있는 요청이라면 로그와 정책을 확인한 뒤 백오프를 적용해 재시도할 수 있어. 무조건 재시도하면 같은 버그를 반복하거나 부작용을 중복 실행할 수 있어.
- 502 Bad Gateway — 게이트웨이나 프록시가 상위 서비스에서 유효한 응답을 받지 못했다는 뜻이야. 일시적 장애일 수 있으므로 요청의 재시도 안전성을 확인한 뒤 백오프를 적용할 수 있어.
- 503 Service Unavailable — 서버가 과부하이거나 점검 중이어서 현재 요청을 처리할 수 없다는 뜻이야. 서버가
Retry-After를 제공하면 클라이언트는 그 시간을 존중해야 해. - 504 Gateway Timeout — 게이트웨이가 상위 서비스의 응답을 제한 시간 안에 받지 못했다는 뜻이야. 상위 서비스의 일시적 지연일 수 있지만, 이 경우에도 메서드의 멱등성과 중복 실행 가능성을 확인하고 재시도해야 해.
cwkPippa의 실제 상태 코드
Location이 포함된 201을, 비동기 Council 마무리에는 클라이언트가 완료 여부를 주기적으로 확인할 수 있는 202를 사용해. archive-folder 삭제에는 204, 형식이 잘못된 chat 페이로드에는 400, 인증 정보가 없으면 401, 관리자가 아닌 사용자가 관리자 라우트를 요청하면 403, Pydantic 필드 검증 실패에는 422를 반환하지. OpenAI나 Anthropic 상위 서비스의 요청 한도에는 Retry-After가 포함된 429, 처리되지 않은 예외에는 500, 백엔드 브레인인 Codex나 Gemini를 사용할 수 없을 때는 Retry-After가 포함된 503을 사용해. 각 선택은 실제 운영에서 클라이언트가 올바르게 대응하도록 다듬어진 결과야.