AniList authentication
Constructor token
Pass the token as the first argument or in the anilist credential slot:
import { AniLink } from "anilink-api-wrapper";
const aniLink = new AniLink("anilist-token");
// or
const aniLink2 = new AniLink({ anilist: { authToken: "anilist-token" } });Multiple instances can hold different tokens, and each exposes its own anilist API. Create a separate instance for each user or bot that needs a different token.
Public versus authenticated operations
Read-only operations (public media, character, staff queries) work without any token:
const aniLink = new AniLink();
const anime = await aniLink.anilist.query.media({ id: 1, type: "ANIME" });Mutations and viewer-scoped queries (viewer, notification, list mutations) require a token. Calling them without one throws AniLinkAuthError.
OAuth2 authorization-code flow
AniLink provides helpers for the OAuth2 authorization-code flow. First, register an application on the AniList developer settings page to get a client ID and secret.
1. Send the user to the authorization URL
import { buildAuthorizationUrl } from "anilink-api-wrapper";
const state = crypto.randomUUID(); // a fresh random value per login attempt
const authorizeUrl = buildAuthorizationUrl("your-client-id", "https://example.com/callback", state);
// Redirect the user to `authorizeUrl`.The third state parameter is optional, but use it to help prevent CSRF attacks. Bind it to the user's session and validate that the returned state matches before exchanging the code.
2. Exchange the code for a token
import { getAccessToken, AniLink } from "anilink-api-wrapper";
const { access_token, refresh_token } = await getAccessToken(
"your-client-id",
"your-client-secret",
code, // the `code` query parameter from the redirect
"https://example.com/callback"
);
const aniLink = new AniLink(access_token);3. Refresh when the token expires
The refresh response may not include a new refresh_token. When it does not, keep using the one you stored:
import { refreshAccessToken } from "anilink-api-wrapper";
const { access_token, refresh_token: rotated } = await refreshAccessToken(
"your-client-id",
"your-client-secret",
refresh_token
);
const nextRefreshToken = rotated ?? refresh_token;4. Refresh proactively
AniList reports the token lifetime as expires_in seconds. Use getTokenExpiry to refresh before the token expires instead of waiting for a 401:
import { getTokenExpiry, refreshAccessToken } from "anilink-api-wrapper";
if (Date.now() >= getTokenExpiry(tokenResponse).getTime() - 60_000) {
tokenResponse = await refreshAccessToken(
"your-client-id",
"your-client-secret",
nextRefreshToken
);
}5. Automatic refresh
Steps 1 through 4 leave token refresh to your code. Set refreshToken, clientId, and clientSecret to let the client refresh automatically. When any AniList request fails with a 401 (an HTTP-level 401, or a GraphQL envelope whose errors entry carries status: 401), the client automatically exchanges the stored refresh token for a fresh access token. It updates its credentials and replays the original request once.
const aniLink = new AniLink({
anilist: {
authToken: token.access_token,
refreshToken: token.refresh_token,
clientId: "your-client-id",
clientSecret: "your-client-secret",
onTokenRefresh: (response) => saveToken(response),
},
});- Opt-in. The client attempts a refresh only when you set all three fields (
refreshToken,clientId, andclientSecret). Otherwise, the call fails with a401. AniList requires the client secret for its refresh grant, but MAL treats it as optional. - Bootstrappable. A client with
refreshToken,clientId, andclientSecret, but noauthToken, refreshes on its first auth-required call. You can construct one from a stored refresh token without an access token. - One replay, no loop. A replay that fails again rejects with that error. There is no retry loop. Concurrent 401s share one refresh grant.
- Failure event.
onTokenRefreshErrorfires exactly once per failed grant with the sanitized token-request error. The failing call rejects with the same error. If the callback throws, the client reports the callback error throughonHookError. The callback error does not replace the token-request error. - Per-call, not per-traversal. Refresh applies per wrapped operation call. Pages already in flight under
paginatewithconcurrency > 1fail independently if they dispatched with the expired token. Only the failing call itself triggers the grant and replay.
Persist synchronously in onTokenRefresh. The client starts using the new token before your callback returns. If the process exits or the callback throws between the refresh and your persistence write, the in-memory client works but your stored credentials are stale. When AniList rotates the refresh token, the stored one becomes permanently invalid. Automatic refresh cannot recover after a restart. Re-authorize manually to resume refresh. The callback receives the effective token response. When AniList omits refresh_token, the stored one stays valid and the response carries it.
Constants and types
ANILIST_AUTHORIZE_URL and ANILIST_TOKEN_URL expose the OAuth endpoints. AniListTokenResponse types the token payload (access_token, token_type, expires_in, refresh_token), handy for your own storage layer.
Next steps
- AniList client configuration, transport settings for the authenticated client.
- Mutations, the operations that require this token.
- MAL authentication, the same automatic-refresh lifecycle on the MAL slot, where the client secret is optional.