문제

요청이 415 Unsupported Media Type로 돌아옵니다. 엔드포인트는 존재하고, 인증도 멀쩡하고, 보낸 본문도 맞아 보입니다. 그런데 서버가 페이로드를 읽기도 전에 거부합니다. 당신이 선언한 Content-Type가 이 라우트에서 받아들이는 타입이 아니기 때문입니다.

증상

  • 같은 URL에 GET은 200인데 POST·PUT·PATCH가 415를 반환합니다.
  • 똑같은 JSON이 Postman에서는 되는데 curl -d나 직접 짠 fetch에서는 실패합니다.
  • 요청 본문에 gzip 압축을 추가한 뒤부터 실패하기 시작했습니다.
  • 프레임워크 로그가 “no acceptable representation” 또는 “Content-Type ‘text/plain’ not supported” 비슷한 말을 합니다.

415의 실제 의미

RFC 9110(§15.5.16)은 415를 오리진 서버가 “콘텐츠가 대상 리소스에서 이 메서드가 지원하지 않는 형식이라 요청 처리를 거부하는” 것으로 정의합니다. 잘 읽으세요: 데이터가 틀렸다는 게 아니라 데이터의 형식 선언에 관한 겁니다. 서버는 당신의 Content-Type 헤더(때로는 Content-Encoding)를 보고, 여기서 그 미디어 타입을 처리할 수 없다고 판단하고, 멈췄습니다.

이건 요청 측 오류이고, 그게 415에 대해 가장 유용한 사실입니다. 406 Not Acceptable은 거울상입니다 — 서버가 당신의 Accept 헤더에 맞는 응답을 만들 수 없다는 뜻이죠. 400은 본문이 잘못된 형식이란 뜻입니다. 422는 본문이 잘 파싱됐지만 검증에 실패했다는 뜻입니다. 415는 그 어느 것도 아닙니다. 본문은 애초에 문제가 아니었습니다. 봉투에 붙은 라벨이 문제였습니다. 서버가 타입 협상 단계에서 당신을 거절했다는 걸 체득하는 순간, JSON 디버깅을 멈추고 헤더 디버깅을 시작하게 됩니다.

명세는 415를 내보내는 서버가 무엇을 받아들이는지 알려줘야(SHOULD) 한다고도 합니다. PATCH라면 Accept-Patch 헤더(RFC 5789)입니다. 어떤 API는 지원 타입을 에러 본문이나 Accept-Post 계열 헤더에 광고합니다. 예의 바른 415는 답을 손에 쥐여줍니다. 게으른 415는 그냥 “Unsupported Media Type”라고만 하고 추측하게 남겨둡니다 — 대개 당신이 이 글을 읽는 이유죠.

주요 원인 3가지

  1. 누락되거나 잘못된 Content-Type 헤더 - 압도적 1위. 본문에 JSON 문자열을 보내면서 Content-Type: application/json을 설정하지 않으면 클라이언트가 다른 걸 기본값으로 씁니다 — curl -d는 application/x-www-form-urlencoded를, 문자열 본문의 맨 fetch는 text/plain을 보냅니다. 서버의 바인딩 계층(Spring의 consumes, ASP.NET의 [Consumes], JSON에만 걸린 Express json() 파서)이 처리하라고 지시받지 않은 타입을 보고, 데이터가 파싱되기도 전에 415로 답합니다.

  2. 서버가 매칭하지 않는 서브타입/charset - application/json; charset=utf-8을 보냈는데 엄격한 엔드포인트는 맨 application/json만 등록했습니다. 또는 API가 application/vnd.api+json 같은 벤더 타입을 기대하는데 그냥 application/json을 보냈습니다. 바이트는 동일한 JSON인데, 미디어 타입 문자열이 서버의 허용 목록과 안 맞아 문자열 비교만으로 거부됩니다.

  3. 서버가 디코드 못 하는 Content-Encoding - 대역폭을 아끼려 요청 본문을 gzip하고 Content-Encoding: gzip을 설정했는데, 서버나 프레임워크가 압축된 요청 본문을 지원하지 않습니다(대부분 그렇습니다 — 압축은 보통 응답 전용 기능입니다). 페이로드를 선언된 미디어 타입으로 디코드할 수 없으니 415를 반환합니다. 클라이언트 압축을 추가하고 서버도 업로드 해제를 옵트인해야 한다는 걸 잊은 사람들이 여기 걸립니다.

DechoNet으로 진단

  • HTTP 진단으로 그 엔드포인트가 깨끗한 요청에 반환하는 정확한 상태 줄과 헤더를 보세요. 415는 특정 Content-Type를 가진 특정 쓰기에 묶여 있으므로, 중립적 진단은 보통 200이나 405로 돌아옵니다 — 그리고 그 갈림이 핵심입니다. 라우트가 평범한 요청에 정상 응답한다면, 415는 망가진 배포가 아니라 당신의 헤더 대 엔드포인트가 소비하는 것에 있습니다. 응답 헤더에서 Accept-Patch나 광고된 미디어 타입을 눈여겨보세요 — 서버가 실제로 원하는 걸 알려주는 겁니다.

해결 체크리스트

  • 당신이 보낸다고 생각하는 게 아니라 클라이언트가 실제로 보내는 Content-Type를 출력하세요. curl -v나 브라우저 네트워크 탭이 진짜 헤더를 보여줍니다. 열에 아홉은 누락됐거나, text/plain이거나, API가 JSON을 원하는데 form-encoded입니다.
  • 헤더를 엔드포인트가 문서화한 값 — 보통 application/json — 으로 명시하세요. API가 벤더 타입(application/vnd.api+json, application/merge-patch+json)을 쓰면 접미사까지 정확히 맞추세요.
  • charset를 확인하세요. 서버가 맨 application/json을 등록했다면 ; charset=utf-8을 빼는 게 해결책 전부일 수 있습니다. 원한다면 추가하세요. 허용 목록과 글자 하나까지 맞추세요.
  • 요청 본문을 압축했다면 Content-Encoding: gzip을 제거하세요(또는 서버가 업로드 해제를 지원하는지 확인). 응답 압축과 요청 압축은 별개 기능이고 대부분 서버는 전자만 합니다.
  • 변장한 406이 아닌지 확인하세요. Accept 헤더가 불일치이고 응답 본문은 멀쩡하다면 당신이 원하는 건 415가 아니라 406입니다 — 다른 헤더, 다른 해결.
  • 요청을 다시 돌려 엔드포인트가 415 대신 2xx를 반환하는지 확인하세요.

에스컬레이션 시점

  • 어제까지 되던 요청이 클라이언트 변경 없이 오늘 415를 반환하기 시작했다면, 전송 중에 Content-Type 헤더를 다시 쓰거나 벗기는 프록시·CDN·WAF를 의심하세요. 직접 요청엔 엔드포인트가 건강하고 엣지를 통하면 415가 나온다는 HTTP 진단 결과는 곧장 미들박스를 가리킵니다.
  • API를 직접 운영하는데 정당한 클라이언트가 지원하려던 타입에서 415를 맞고 있다면, 엔드포인트의 허용 미디어 타입을 넓히고 415 본문이 무엇을 소비하는지 나열하게 하세요 — Accept-Patch도 허용 목록도 없는 맨 “Unsupported Media Type”는 지원 티켓을 예약하는 셈입니다.

내 도메인에서 바로 확인

무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.