A CDN in front of an API needs a Vary header

A cache keys on the URL. If the response depends on anything else — an Accept header, a language, an authorisation token — the cache will serve one variant to everyone unless told what else matters.

return response()->json($data)
    ->header('Vary', 'Accept, Accept-Language')
    ->header('Cache-Control', 'public, s-maxage=300');

Vary: Authorization looks like the safe way to cache authenticated responses and is not — it fragments the cache per token and stores private data in a shared cache, so private is the correct answer there instead. Varying on User-Agent is the classic mistake: it is effectively unique per visitor and disables caching entirely while appearing to work.