A limit on attempts is not a substitute for a good password. It is what makes a good password matter: without one, the difference between a password somebody could guess and one they could not is a few hours of somebody else's computer.
It belongs on every endpoint worth guessing at, which is more of them than the login — a reset form, a token exchange, an endpoint that says whether a code is right.
POST /tokenSend this several times. One of these builds eventually stops answering.
A reset token has the same shape of problem in a different place. It has to be unguessable, and unique is not the same as unguessable — a token built from an address and the current minute is unique to that address and that minute, and both of those are things an attacker also has.
The fix is to take it from somewhere nobody else can reach: sign it with a key only you hold, or draw it from a real source of randomness. Deriving it from the request is deriving it from something the requester controls.