Ошибка 415 Unsupported Media Type означает, что сервер отказался обработать запрос из-за неподдерживаемого формата его содержимого. Сначала сверьте три вещи: какой формат принимает конкретный адрес API, что указано в заголовке Content-Type и какие данные действительно отправлены.
Ниже — порядок проверки для вебмастера или разработчика, у которого перестала отправляться форма, загрузка файла или запрос интеграции.
1. Найдите запрос, который получил 415
Откройте инструменты разработчика → Network («Сеть»), выполните проблемное действие на тестовом стенде и выберите запрос с кодом 415. Смотрите его URL, метод, Request Headers и Payload: главная страница может отвечать успешно, пока отдельный POST-запрос отклоняется.
Зафиксируйте время с часовым поясом и безопасный идентификатор запроса, если он есть. В ответе проверьте подсказку об ожидаемом типе.
Не публикуйте Cookie, Authorization, токены из URL и тело с данными клиента; повтор оплаты или заказа ради диагностики может создать реальную операцию.
2. Сверьте формат с контрактом API
Контракт — документация именно этого адреса и метода, а не общая инструкция для всего сервиса. Один маршрут может принимать JSON, другой — поля формы, третий — файл отдельным потоком.
| Ожидаемый формат | Заголовок Content-Type | Что должно быть в теле | |---|---|---| | JSON | application/json | JSON, например объект с полями | | Обычная форма | application/x-www-form-urlencoded | Пары вида key=value с URL-кодированием | | Форма с файлом | multipart/form-data; boundary=… | Части с разделителем boundary | | Двоичный поток | Тип, разрешённый контрактом | Байты файла без обёртки формы |
Если контракт требует специальный тип вроде application/vnd.example+json, используйте его: поддержка application/json не подразумевается.
Content-Type описывает отправляемое содержимое. Accept в запросе сообщает, какой ответ нужен клиенту; замена Accept не исправляет формат тела. Ошибка 406 относится к другой задаче — выбору приемлемого представления ответа.
По RFC 9110, раздел 15.5.16, причиной 415 может быть также Content-Encoding или проверка самих данных. В ответе Accept иногда подсказывает допустимые типы, а Accept-Encoding — допустимое сжатие запроса; отсутствие этих подсказок не отменяет ошибку.
3. Проверьте свою ветку
JSON: заголовок и сериализация должны совпадать
Сравните фактическое тело с ожидаемым JSON. Строка вида key=value остаётся данными формы, даже если сверху написать application/json; объект JavaScript перед отправкой через fetch обычно нужно сериализовать с JSON.stringify.
Проверьте, не превратился ли объект в строку "[object Object]" и не применили ли JSON.stringify дважды.
Ошибочный синтаксис или недопустимые поля могут вызвать другие коды, например 400 или 422: точный выбор зависит от приложения. Не пытайтесь определить единственную причину только по номеру статуса — читайте ответ и серверные логи.
Файл: не задавайте boundary вручную
При передаче браузерного FormData через fetch или XMLHttpRequest уберите самостоятельно заданный Content-Type. Браузер сформирует multipart/form-data вместе с boundary, который соответствует разделителям в теле; вручную оставленный заголовок без него ломает эту связку. Это правило описано в документации MDN о FormData.
Проверьте имя поля файла и допустимый тип каждой части по контракту. Если API ожидает необёрнутые байты изображения, отправка multipart-формы всё равно будет неверной; переименование расширения файла его содержимое не меняет.
Сжатие: проверьте Content-Encoding запроса
Если клиент добавляет Content-Encoding: gzip, выясните, поддерживает ли этот маршрут сжатое тело и действительно ли оно сжато. Для сравнения на стенде отправьте тот же допустимый документ без сжатия и без этого заголовка.
Не путайте это с gzip или Brotli в ответе сервера: здесь проверяется содержимое, которое клиент отправляет серверу. Не отключайте сжатие всего сайта ради одной операции.
4. Сделайте минимальный контрольный запрос
На тестовом адресе, который по документации принимает POST с JSON и не запускает реальные операции, можно проверить заголовок и небольшой документ отдельно от интерфейса:
curl -i 'https://example.test/api/validate' \
-H 'Content-Type: application/json' \
--data-binary '{"test":true}'
Адрес и поле условные: замените их разрешённым тестовым маршрутом и данными из его контракта. Пример не проходит авторизацию и не должен отправляться в боевой endpoint оплаты, заказа или рассылки.
В документации curl указано, что --data-binary сохраняет передаваемые данные, но его тип по умолчанию — application/x-www-form-urlencoded. Поэтому для JSON заголовок задан явно; curl не проверяет соответствие тела контракту API.
Проверяйте по очереди формат с соответствующим заголовком, сжатие и версию маршрута. Если контрольный запрос проходит, а форма нет, сравните их фактические запросы, включая конечный URL после перенаправления.
5. Найдите слой отказа и подтвердите исправление
Если корректный по контракту запрос всё ещё получает 415, сопоставьте его время и идентификатор в логах прокси и приложения. Если запись есть у прокси, но отсутствует у приложения, проверьте шлюз или фильтр перед ним. Убедитесь, что смотрите нужный узел и журнал.
Если запрос дошёл до приложения, проверьте обработчик маршрута, включённые парсеры тела и изменения после релиза. Для этого пригодится руководство как найти запрос в логах сервера; не расширяйте список принимаемых форматов без проверки последствий.
Исправление подтверждается исходным сценарием: данные приняты, получен ожидаемый результат и не появились лишние записи. Успешный GET главной страницы или health endpoint не проверяет проблемную отправку; границы таких проверок разобраны в статье о мониторинге API.
Следующий шаг
Если нашли несоответствие заголовка и тела, исправьте его в клиенте и добавьте проверку этого формата в тесты интеграции. Отдельный контроль доступности URL полезен для наблюдения за сервисом, но не заменяет тест отправки данных.
Если слой отказа остаётся непонятным, через форму Web-Puls можно отправить заявку на диагностику: укажите публичный адрес, время, действие, ожидаемый формат и безопасное описание ответа. Формат работ и стоимость согласуют до начала работ; пароли и полное тело запроса для первичной оценки не нужны.