GuidesANIME APIS

MyAnimeList API from browser JavaScript

The MyAnimeList API at api.myanimelist.net/v2 sends no Access-Control-Allow-Origin header, and on 28 October 2026 its preflight answered 405, so any fetch() that adds X-MAL-CLIENT-ID is blocked before it leaves the browser. The free cors.dev proxy drops that header, and MAL answers 403 without it. Add the header from a backend you control or, for internal tools and apps where each user brings their own client ID, through cors.dev Pro ($5 a month for 500,000 requests).

Why fetch() to the MyAnimeList API fails

The official MyAnimeList API (still labelled beta) lives at https://api.myanimelist.net/v2. Public reads need your client ID in the X-MAL-CLIENT-ID header; anything that touches a user's own list needs an OAuth 2.0 token in Authorization: Bearer.

X-MAL-CLIENT-ID is not a CORS-safelisted header, so the browser sends an OPTIONS preflight before your GET. MAL answers it 405 {"message":"","error":"method_not_allowed"} with no CORS headers, so the browser never sends the GET. Chrome reports "Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource."

What we saw from a browser origin

Each request below carried Origin: https://example.com, like a page on another site, and was repeated through the free cors.dev proxy. No response from api.myanimelist.net carried an Access-Control-Allow-Origin header.

MyAnimeList requests from a browser origin, checked October 2026
RequestDirect from a pageFree cors.dev proxy
Anime search with X-MAL-CLIENT-IDPreflight 405, no CORS headerHeader dropped, 403 forbidden
Anime search without a client ID403 forbidden, no CORS header403 forbidden
Anime ranking without a client ID403 forbidden, no CORS header403 forbidden
OAuth token exchange401, preflight 405, no CORS header405, GET and HEAD only
Cover image on cdn.myanimelist.net200, CORS for any origin200
Jikan anime detailsTimed out after 10 s522 after 19 s

The free proxy forwards only Accept, Accept-Language, If-None-Match, If-Modified-Since and Range. It dropped X-MAL-CLIENT-ID, named it in X-Cors-Dropped-Headers, and MAL answered 403. A client_id query parameter is ignored (also 403), so the free tier cannot reach the MAL API. From a server, a made-up client ID returns 400 "Invalid client id", which tells you the header arrived.

What a client ID unlocks

A client ID covers catalog data, rankings, seasonal charts, forum topics and user lists. The API leaves most fields out unless you name them in fields, for example fields=mean,num_episodes,genres, and some endpoints skip NSFW entries unless you add nsfw=true.

MAL API v2 read endpoints, checked October 2026
EndpointReturnsMax limitNeeds
/v2/anime?q=Anime search100Client ID
/v2/anime/{anime_id}One anime, the fields you ask forNot a listClient ID
/v2/anime/rankingTop anime by ranking_type500Client ID
/v2/anime/season/{year}/{season}Anime of one season500Client ID
/v2/users/{user_name}/animelistA user's anime list1000Client ID
/v2/manga?q=Manga search100Client ID
/v2/manga/{manga_id}One mangaNot a listClient ID
/v2/manga/rankingTop manga500Client ID
/v2/users/{user_name}/mangalistA user's manga list1000Client ID
/v2/forum/topicsForum topics100Client ID
/v2/anime/suggestionsSuggestions for the signed-in user100OAuth token
/v2/users/@meThe signed-in user's profileNot a listOAuth token

MAL publishes no request-rate numbers and documents 403 as "DoS detected etc.", so cache what you fetch.

Keep the client ID on a backend

MAL's API License and Developer Agreement calls the client ID "the security key" for the API and says "You may not share your Client ID with any third party, must keep your Client ID and all log-in information secure". Anything in browser JavaScript is readable by every visitor, so a public site should add the header on a server.

The Worker below keeps the ID in a secret, allows only GET on read-only paths, caches successful responses at the edge for 600 seconds and returns Access-Control-Allow-Origin for your site. In our test it passed MAL's answer through with the CORS header and returned 404 for /v2/users/@me, /v2/anime/suggestions and any POST.

Cloudflare Worker that adds X-MAL-CLIENT-ID
// Cloudflare Worker; store the ID with: wrangler secret put MAL_CLIENT_ID
const ALLOWED = /^\/v2\/((anime|manga)(\/\d+|\/ranking|\/season\/\d{4}\/(winter|spring|summer|fall))?|users\/[\w-]+\/(anime|manga)list)$/;
const APP_ORIGIN = 'https://your-app.example';

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    if (request.method !== 'GET' || !ALLOWED.test(url.pathname)) {
      return new Response('Not found', { status: 404 });
    }

    // Cache successful answers at the edge, even though MAL sends no-store
    const upstream = await fetch(`https://api.myanimelist.net${url.pathname}${url.search}`, {
      headers: { 'X-MAL-CLIENT-ID': env.MAL_CLIENT_ID },
      cf: { cacheEverything: true, cacheTtlByStatus: { '200-299': 600 } },
    });

    const response = new Response(upstream.body, upstream);
    response.headers.set('Access-Control-Allow-Origin', APP_ORIGIN);
    return response;
  },
};

cors.dev Pro for internal tools and your own client ID

cors.dev Pro forwards X-MAL-CLIENT-ID and other custom headers, plus POST, PUT, PATCH and DELETE, from origins you register; the publishable Connection key goes in X-Cors-Key. It does not hide the client ID: the header travels through cors.dev to MAL and stays readable in the page.

That fits three cases:

  • Internal tools that only your team opens.
  • Prototypes running on localhost.
  • Apps where each user registers their own MAL client and pastes the ID into your UI.

Pro costs $5 a month for 500,000 requests, with a 7-day trial of 1,000 requests and no card. Its X-Cors-Cache option caches a GET only when the upstream allows it, and every MAL API response we saw carried Cache-Control: max-age=0, no-cache, no-store, so cache in your own code or in the Worker above.

Top airing anime through cors.dev Pro
// Each user pastes their own client ID; anything in page code is visible to visitors
const clientId = document.querySelector('#mal-client-id').value;
const params = new URLSearchParams({ ranking_type: 'airing', limit: '10', fields: 'mean,num_episodes' });

const response = await fetch(`https://proxy.cors.dev/https://api.myanimelist.net/v2/anime/ranking?${params}`, {
  headers: {
    'X-Cors-Key': 'YOUR_CONNECTION_KEY', // publishable, works only from your registered origins
    'X-MAL-CLIENT-ID': clientId,
  },
  credentials: 'omit',
});
if (!response.ok) {
  throw new Error(`${response.status} ${response.headers.get('X-Cors-Error') ?? (await response.text())}`);
}

const { data } = await response.json();
for (const { node, ranking } of data) {
  console.log(ranking.rank, node.title, node.mean, node.num_episodes);
}

OAuth, images and Jikan

MAL's OAuth flow is the authorization code grant with PKCE, and only the plain challenge method is supported. Codes are exchanged at POST https://myanimelist.net/v1/oauth2/token, which sends no CORS header either. If your client has a client secret, that request has to carry it, so it belongs on a server. Refresh tokens last one month.

Cover art on cdn.myanimelist.net answered 200 with Access-Control-Allow-Origin: *, so <img> tags and canvas reads work without a proxy.

Jikan (api.jikan.moe/v4), the unofficial MAL API that needs no client ID, timed out after 10 seconds when we called it directly on 28 October 2026, and failed with 522 through cors.dev, so we could not use it that day.

Good questions.

Can I send the MAL client ID as a query parameter to skip the preflight?

No. MAL ignored a client_id query parameter and answered 403 forbidden. The ID only works in the X-MAL-CLIENT-ID header, and that header always triggers a preflight in the browser.

How do I get a MyAnimeList API client ID?

Sign in to MyAnimeList and open myanimelist.net/apiconfig, which redirects to the login page otherwise, then register your app. MAL issues a 32-character client ID and, for clients that have one, a 64-character client secret.

Is the MyAnimeList API free?

The API agreement charges no fees, but MAL reserves the right to start charging after notice and to limit usage. Commercial apps need MAL's written permission before they earn money, and scraping the website is not allowed.

Why does the MAL API leave out the score and episode count?

Most fields are only returned when you list them in fields, for example fields=mean,num_episodes,genres. Some endpoints also skip NSFW entries unless you add nsfw=true.

Does cors.dev Pro keep my MAL client ID secret?

No. Pro forwards the header you send from the browser, so the ID stays visible in your code and the network tab. For a public site, keep it in a backend such as the Worker above.