What is the difference between HTTP 401 and 403?
The short version is that 401 concerns identity and 403 concerns permission. A 401 means the request was not authenticated: no credentials were supplied, or the ones supplied were not valid. A 403 means the request was authenticated successfully, the server knows who you are, and that identity is not allowed to do this.
The naming is unhelpful, because the specification calls 401 Unauthorized when it really means unauthenticated. That mismatch is the source of most of the confusion, and the reason so many APIs use the two interchangeably.
The practical difference is what the client should do next. A 401 suggests retrying with credentials, or refreshing an expired token, is worth attempting. A 403 says retrying with the same identity is pointless and the client should stop rather than loop. Client libraries frequently implement automatic token refresh on 401, which is exactly why returning 401 for a permission failure causes an endless refresh loop against a token that was never the problem.
A correct 401 response should also include a header describing the authentication scheme the client should use. This is widely omitted in practice, but it is part of what makes the status meaningful.
There is a legitimate reason to blur the line deliberately. Returning 403 for a resource that exists but is not yours confirms its existence, which leaks information. Some APIs return 404 in that situation instead, so nobody can enumerate valid identifiers by watching which ones return forbidden rather than not found. That is a security decision rather than a correctness one, and worth making consciously.
For an API other people will consume, using the two accurately saves those consumers a great deal of guesswork.