HTTP caching headers the application controls

A reverse proxy or CDN caches what the response tells it to cache, which means the decision belongs in the application and usually is not made at all — a missing header leaves it to the proxy’s defaults.

return response()->json($data)
    ->header('Cache-Control', 'public, max-age=60, s-maxage=300')
    ->setEtag(md5($serialised));

// private data must say so, or a shared cache may store it
->header('Cache-Control', 'private, no-store');

s-maxage applies to shared caches only, so a CDN can hold something for five minutes while a browser holds it for one. The private, no-store case is the one worth being paranoid about: an authenticated response cached by a proxy is served to the next person, and it is a configuration mistake rather than an exotic attack.