Honestly, best practices depends on what you are trying to achieve. Why is the API painful to use? Try to address those aspects when creating your client library.

Many API's are easy enough to understand, they don't necessarily get lots of benefit from an official client library. Developers just write the HTTP request code themselves for whichever endpoints they are using. keep it simple if you are first starting.

create a separate file, NameAPI.js, to house your endpoint calling functions. Make it easy for the caller to provide necessary values to those functions. Sometimes API endpoints have a lot of extra optional params/properties. Don't worry about including those in the function interface unless you are actually using them for your use-case--instead choose suitable default params/properties whenever possible.

Then continue to build out your client functions as you need more endpoints or endpoint features.

I would say sometimes its helpful to provide multiple client functions per one endpoint, especially if this endpoint is already loaded with lots of different choices for params/properties. EX: ive seen create user endpoints that require a non-descriptive type property like 1 = basic user 2= admin user etc. well, you could have two client functions createUser vs createAdminUser. In this way, you are taking off some of the burden for the caller to figure out which type number they need to pass, and instead giving them natural language functions which configure and run the HTTP request for them.

A nice to have: make it easy for the caller to pass properties using host language conventions like property name casing. ex: if the host language is in javascript, it may be easier/more consistent for the caller to always provide camelCased params/properties to the function, even if the API endpoint expects snake_case for params/properties (translate the names for your caller).

be clear about how you are handling error conditions in your client functions/package. I think of two basic types of error conditions, network errors vs operational errors. Network errors would be when client has not internet connection or times out for whatever reason. Operational error would be when server DOES send back response but its an error response (like 400's client provided bad values or 500's server is malfunctioning). And will you be throwing errors on network errors AND operational errors? or just throwing on network errors? or not throwing at all (returning some value instead)? basically, how does the caller know when there was an error and what type of error? In Go, i've seen sql clients go through the trouble of returning custom error types for every possible sql error that could happen. That gives the client more potential options to decide how to recover/resume.

If you are first starting, I honestly wouldn't worry about including auth handling within the client. I find it usually confusing more than helpful. But it depends on the protocol and auth method. Perhaps auth IS one of the most painful points of using the API, in that case building some auth management into the client may be helpful. But HTTP + Authorization token is so straightforward and common, developers can manage that auth cred easily in their own way. In that case, just make the token a parameter that must be passed to the client function. That's the simplest way to start.

Next option might be to allow the caller to create an instance of the client for a specific auth cred. Then the caller can use this instance whenever it needs to call client functions given that auth cred, or create multiple instances each with a separate user/service auth cred.

if you are hosting this client as a package for others to use in the community, be sure to stay on top of any API changes which necessitate your client package to be updated. Provide clear documentation about any API changes and how your package has addressed them.