Rate limiting per client, with the header that says so

A limit with no feedback means a well-behaved client cannot behave well — it finds out by being refused, and its only strategy is to retry.

return response()->json($data)
    ->header('X-RateLimit-Limit', 1000)
    ->header('X-RateLimit-Remaining', $remaining)
    ->header('X-RateLimit-Reset', $resetsAt->getTimestamp());

// and on refusal, the header that changes behaviour
return response()->json(['error' => ['code' => 'rate_limited']], 429)
    ->header('Retry-After', 42);

Retry-After is the one that matters, because a client without it backs off by guessing and the guesses are wrong in both directions. Limiting per authenticated client rather than per IP is the other half — an office behind one address is one client under an IP limit, which is a support ticket waiting. A sliding window is a dozen lines and is considerably fairer than a fixed one, which allows double the limit across a boundary.