Making POST request with AJAX returns a 400 error (without jQuery)

Thank you Sally CJ for helping me out! The problem was that the data needs to be sent as a serialized string (query string) and my request header was wrong.

Here is the final working function I have now. Sorry about the extraneous stuff about the loading animation and the scrolling to the top of the results list. Hopefully this helps someone in the future.

function kp_submit_filters(){
// console.log( 'START kp_submit_filters()' );
// console.log( window.mirrored_params );

// Start the loading animation.
var loading_indicator = document.querySelectorAll('.sk-circle');
if( loading_indicator.length ){
    kpAddClass(loading_indicator, 'is-loading');
}

// Scroll to the top of the results.
var target = document.querySelectorAll('.results');
if (target.length) {
    var target_shape = target[0].getBoundingClientRect();
    var target_position = target_shape.top + window.pageYOffset;
    window.scroll({
        top: target_position - 60,
        left: 0,
        behavior: 'smooth'
    });

}


// Set up some data to send in the AJAX request.
var base_url = kp_get_base_url();

var data = window.mirrored_params;
    data.action = 'kp_filters';
    data.nonce = Knuckle.nonce;
    data.base_url = base_url;

// Serialize and encode the data object.
var serialized_data = encodeURI( kp_serialize(data) );

// console.log('About to post this data: ');
// console.log( data );
// console.log( serialized_data );

// Make the request object.
var xhr = new XMLHttpRequest();

// Set up what happens with the response - remove existing result items and pagination, add the new search result items and pagination to the DOM, and also stop the loading animation.

xhr.onreadystatechange = function() {
    var DONE = 4; // readyState 4 means the request is done.
    var OK = 200; // status 200 is a successful return.
    if( xhr.readyState === DONE ){
        // console.log('ajax is done: ' + xhr.status);
        if( xhr.status === OK ){
            // console.log('status is OK');
            // console.log(xhr);
            // console.log(xhr.responseText); // 'This is the returned text.'

            var response = JSON.parse( xhr.responseText );
            var result_items = document.querySelectorAll('.results-item');
            var result_pagination = document.querySelectorAll('.results-pagination');
            var results_list = document.querySelectorAll('.results-list');

            if( response.results && response.results.length ){

                // Remove the existing items.
                for( i=0; i<result_items.length; i++){
                    result_items[i].parentNode.removeChild(result_items[i]);
                }

                // Remove the existing pagination.
                for( i=0; i<result_pagination.length; i++){
                    result_pagination[i].parentNode.removeChild(result_pagination[i]);
                }

                // Add the new items and pagination to the DOM.
                if( results_list.length ){
                    for(i=0; i<response.results.length; i++){
                        results_list[0].innerHTML += response.results[i];
                    }

                    if( response.pagination && response.pagination.length ){
                        results_list[0].innerHTML += response.pagination;
                    }

                }



            } else {
                // No results, so just remove the existing items and pagination.

                for( i=0; i<result_items.length; i++){
                    result_items[i].parentNode.removeChild(result_items[i]);
                }

                for( i=0; i<result_pagination.length; i++){
                    result_pagination[i].parentNode.removeChild(result_pagination[i]);
                }

            }

        }

        // Hide the loading animation.
        if( loading_indicator.length ){
            kpRemoveClass(loading_indicator, 'is-loading');
        }

    } else {
        // console.log('Error: ' + xhr.status); // An error occurred during the request.
    }

};

// Send the POST request via AJAX.
xhr.open('POST', Knuckle.ajaxurl, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
xhr.send( serialized_data );

return false;
}