register_post_meta with show_in_rest exposes the field, and the default authorisation is more permissive than most people assume.
register_post_meta( 'product', '_internal_cost', array(
'type' => 'number',
'single' => true,
'show_in_rest' => true,
'auth_callback' => function ( $allowed, $meta_key, $post_id ) {
return current_user_can( 'edit_post', $post_id );
},
) );
A meta key beginning with an underscore is protected for the classic editor and that protection is not what governs the REST exposure — without an auth_callback, the check is edit_post_meta, which is broader than intended for anything that is genuinely internal. Reading the endpoint as an unauthenticated user after registering any meta field is a ten-second check worth doing every time.