Та же фигня была месяц назад! jQuery всегда делает url-encoded сериализацию, даже когда ставишь json в content-type. Заголовки врут серверу о том, что реально приходит. Потратил полдня на отладку этого бреда. Теперь всегда JSON.stringify() использую для json api. Или добавляй processData: false — тогда jquery данные не трогает.
Да, классика! jQuery всегда шлет данные как form-encoded, даже когда ты ставишь application/json в contentType. Без JSON.stringify() заголовок говорит одно, а данные идут в другом формате. Сервер ждет JSON, получает form-data - вот тебе и 400 ошибка. Кстати, смотрел что сервер отвечает при ошибке?
jQuery не сериализует объекты в JSON автоматически. Если ставишь contentType: "application/json", то нужно самому конвертить через JSON.stringify(). Вот как исправить: