wp_die is the wrong way to fail an AJAX request

wp_die() in an AJAX handler sends an HTML error page with a 200 status, so the JavaScript sees a successful response containing markup and fails while parsing it. The user gets nothing and the console says something about unexpected token.

add_action( 'wp_ajax_save_thing', function () {
    if ( ! current_user_can( 'edit_posts' ) ) {
        wp_send_json_error( array( 'message' => 'Not allowed.' ), 403 );
    }

    wp_send_json_success( array( 'id' => $id ) );
} );

wp_send_json_error() sets the content type, wraps the payload in the envelope the admin JavaScript expects, and — with the second argument — sets a real status code. Both functions call wp_die() internally, so nothing after them runs. Registering only wp_ajax_ and not wp_ajax_nopriv_ is the other half of the authorisation story.