Headers already sent error with get_template_part in REST API call

I have a custom WordPress REST API endpoint that returns the output
of get_template_part

Note that the REST API expects your endpoint callback to always return the response (e.g. the template part output in your case), and not echo it or anything else. And after the callback is called, the REST API will send some headers, by default an Allow header and then any extra headers from your response — see WP_REST_Server::serve_request(), particularly lines #440 and #472.

So because of that, you should not echo anything in your callback, because for example echo 'foo'; header( 'Allow: GET' ); will cause the “headers already sent” error because the echo started the output, hence PHP issues a warning if you try to send a (HTTP) header afterwards.

Therefore, despite you said, “The error started when I started making these requests.“, I’m pretty sure the problem is with your call to get_template_part() which (despite the name) actually simply loads the template part and if successful, the function returns nothing.

And if the template part echo something (which is what happens in most cases) just like yours as in the question, then that will result in the “headers already sent” warning. Try commenting out the get_template_part() call and see if the warning goes away. 🙂

But even if not, please ensure your callback is not echoing anything because doing so will invalidate the JSON response (both header and content) which is the default response type returned by the REST API endpoint. However, if you need to echo something, then you would want to use output buffering like so in your case:

function build_home() {

    // ... your code.
    get_template_part( ... your code ... );
    $data = ob_get_clean();

    // The array key ("html") below can be any other name that you prefer.
    return new WP_REST_Response( array(
        'html' => $data,
    ) );

And please always set the permission_callback for your endpoint, because otherwise, you’d get a (_doing_it_wrong()) notice like so:

The REST API route definition for theme/v2/homepage is missing the required permission_callback argument. For REST API routes that are intended to be public, use __return_true as the permission callback.

So for example, your code would look like so:

    'theme/v2',  // namespace
    '/homepage', // route
    array(       // options
        'methods'             => 'GET',
        'callback'            => 'build_home',
        'permission_callback' => '__return_true', // always set a permission callback

And BTW, if you want a different response type (i.e. Content-Type) for your endpoint, then you can use the rest_pre_serve_request hook — see an example in my answer here.