Mr.Anime

Mr.Anime API

A free REST API over the same data the site uses — search, trending, seasonal and per-title details. Authenticate with a personal bearer token.

Base URL: https://mr-anime.vercel.app/api/v1

Quickstart

  1. Create a token on your account page (API tokens card). It is shown once — copy it.
  2. Send it as a bearer token on every request.
curl -H "Authorization: Bearer ma_live_YOUR_TOKEN" \
  "https://mr-anime.vercel.app/api/v1/anime/search?q=naruto&perPage=5"
// JavaScript
const res = await fetch("https://mr-anime.vercel.app/api/v1/anime/trending?perPage=10", {
  headers: { Authorization: `Bearer ${process.env.MRANIME_API_TOKEN}` },
});
const { data, pageInfo } = await res.json();

Authentication

Every request needs a token in the Authorization header as Bearer ma_live_… (an X-API-Key header works too). Tokens start with ma_live_, are stored hashed server-side, and only the SHA-256 hash is kept — losing the token means revoking it and creating a new one. Revocation takes effect immediately.

Rate limit: 60 requests per minute per token. Over-limit responses are 429 with a Retry-After header; every success carries X-RateLimit-Remaining. Responses are CDN-cached for 10 minutes to 1 hour, so repeat reads are cheap.

CORS is enabled (Access-Control-Allow-Origin: *), so the API can be called directly from browser apps.

OAuth 2.0 (third-party apps)

Mr.Anime is also an OAuth 2.0 provider — register an app on your account page (OAuth applications card) to get a client_id and a one-time client_secret. Two grant types are supported:

  • authorization_code — “Sign in with Mr.Anime”: users approve your app, you receive a single-use 5-minute code, then exchange it for a 30-day access token.
  • client_credentials — server-to-server API access with no user (read scope only).

Scopes

  • read (default) — all /api/v1 anime endpoints.
  • profile — additionally /api/v1/me (the user’s id, username, avatar, badges). Always requested alongside read by sign-in buttons.

1. Redirect the user

https://mr-anime.vercel.app/oauth/authorize
  ?client_id=CLIENT_ID
  &redirect_uri=YOUR_CALLBACK
  &response_type=code
  &scope=read%20profile
  &state=RANDOM_UNGUESSABLE_STRING

The user signs in and clicks Approve; we redirect back to redirect_uri with code and state (or error=access_denied if they decline). Verify state matches what you sent.

2. Exchange the code

curl -X POST https://mr-anime.vercel.app/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  -d code=CODE \
  -d redirect_uri=YOUR_CALLBACK \
  -d client_id=CLIENT_ID \
  -d client_secret=CLIENT_SECRET

# → { "access_token": "ma_at_…", "token_type": "Bearer",
#     "expires_in": 2592000, "scope": "read profile" }

Server-to-server (no user)

curl -X POST https://mr-anime.vercel.app/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=client_credentials \
  -d client_id=CLIENT_ID \
  -d client_secret=CLIENT_SECRET

# → { "access_token": "ma_at_…", "expires_in": 2592000, "scope": "read" }

3. Use the access token

curl -H "Authorization: Bearer ma_at_YOUR_TOKEN" \
  "https://mr-anime.vercel.app/api/v1/me"   # or any anime endpoint

# → { "data": { "id": "…", "username": "…", "avatarUrl": null,
#               "role": "user", "isVerified": false, "badges": [] } }

Tokens live 30 days and are revocable from your account page (revoking the app kills its tokens instantly). Errors from /oauth/token follow RFC 6749: invalid_client, invalid_grant, unsupported_grant_type, invalid_scope.

Endpoints

MethodPath (under /api/v1)Description
GET/anime/trendingCurrently trending anime, newest first.
GET/anime/popularMost popular anime of all time.
GET/anime/topHighest-rated anime.
GET/anime/seasonalBrowse a season (defaults to the current one).
GET/anime/searchSearch with filters (q, genre, year, status…).
GET/anime/{id}Full details for a single anime by AniList id.
GET/anime/{id}/episodesEpisode list with titles and thumbnails.
GET/anime/{id}/episodes/{episode}Player payload: metadata + episode + embed URLs + dub availability.
GET/meIdentity of the token's user (requires the `profile` scope or a personal token).

Common query parameters

  • page — 1…500, default 1.
  • perPage — 1…50, default 24.
  • search: q (max 200 chars), genre, year (1950–2100), season (WINTER|SPRING|SUMMER|FALL), format (TV|TV_SHORT|MOVIE|SPECIAL|OVA|ONA|MUSIC), status (RELEASING|FINISHED|NOT_YET_RELEASED|CANCELLED), sort (SEARCH_MATCH|TRENDING_DESC|POPULARITY_DESC|SCORE_DESC|START_DATE_DESC|FAVOURITES_DESC).
  • seasonal: season and year default to the current season and year.
  • episodes endpoints: season (1…9999, default 1 — the site’s ?s=) selects the TMDB season used for titles/thumbnails; the player endpoint also takes lang = sub | dub (default sub), mirroring the site’s ?lang=.

Responses

List endpoints return data (an array of anime) plus pageInfo; the detail endpoint returns a single anime under data. The anime shape is identical everywhere:

FieldTypeNotes
idnumberAniList id — use it on `/anime/{id}` and the site (`/anime/{id}`).
titleobject`romaji`, `english`, `native` strings (nullable).
formatstring`TV`, `MOVIE`, `OVA`, `ONA`, `SPECIAL`… (nullable).
statusstring`RELEASING`, `FINISHED`, `NOT_YET_RELEASED`, `CANCELLED`, `HIATUS` (nullable).
episodesnumber | nullTotal episode count.
durationnumber | nullMinutes per episode.
season / seasonYearstring / number`WINTER`, `SPRING`, `SUMMER`, `FALL` + year.
startDate / endDateobject`{ year, month, day }`, nullable parts.
score / meanScorenumber | nullScore as 0–100.
popularity / favouritesnumber | nullAniList popularity counts.
genresstring[]Genre names.
descriptionstring | nullPlain-text synopsis (HTML removed).
coverobject | null`{ extraLarge, large, color }` image URLs.
bannerstring | nullBanner image URL.
studiosstring[]Studio names.
nextAiringEpisodeobject | null`{ episode, airingAt }` — `airingAt` is a Unix timestamp.
isAdultbooleanAdult flag.
GET /api/v1/anime/16498

{
  "data": {
    "id": 16498,
    "type": "ANIME",
    "title": { "romaji": "Shingeki no Kyojin", "english": "Attack on Titan", "native": "進撃の巨人" },
    "format": "TV",
    "status": "FINISHED",
    "episodes": 25,
    "duration": 24,
    "season": "SPRING",
    "seasonYear": 2013,
    "startDate": { "year": 2013, "month": 4, "day": 7 },
    "endDate": { "year": 2013, "month": 9, "day": 29 },
    "score": 84,
    "meanScore": 84,
    "popularity": 16800000,
    "favourites": 320000,
    "genres": ["Action", "Drama", "Fantasy"],
    "description": "Centuries ago, mankind was slaughtered to near extinction…",
    "cover": { "extraLarge": "https://s4.anilist.co/…", "large": "https://s4.anilist.co/…", "color": "#e4784c" },
    "banner": "https://s4.anilist.co/…",
    "studios": ["Wit Studio"],
    "nextAiringEpisode": null,
    "isAdult": false
  }
}
GET /api/v1/anime/trending?perPage=2

{
  "data": [ { "id": 16498, "type": "ANIME", "title": { … }, … } ],
  "pageInfo": { "currentPage": 1, "hasNextPage": true, "total": 500 }
}

/anime/{id}/episodes mirrors the site’s episode grid — titles and thumbnails come from TMDB first, then AniList’s streaming list:

GET /api/v1/anime/16498/episodes

{
  "data": [
    { "number": 1, "title": "To You, in 2000 Years", "thumbnail": "https://image.tmdb.org/t/p/w780/…" },
    { "number": 2, "title": "That Day", "thumbnail": "https://image.tmdb.org/t/p/w780/…" }
  ],
  "meta": { "episodes": 25, "season": 1 }
}

/anime/{id}/episodes/{episode} mirrors the watch page: full metadata, resolved episode info and one ready-to-embed url per provider (in switcher order), with per-provider dub availability probed the same way the site does:

GET /api/v1/anime/16498/episodes/1?lang=dub

{
  "data": {
    "anime": { … the same object as GET /anime/{id} … },
    "episode": { "number": 1, "title": "To You, in 2000 Years", "thumbnail": "https://image.tmdb.org/t/p/w780/…" },
    "season": 1,
    "requestedLang": "dub",
    "effectiveLang": "sub",
    "dubAvailable": false,
    "dubMissing": true,
    "providers": [
      { "id": "provider-1", "name": "Megaplay", "url": "https://…/1/sub", "dubAvailable": false },
      { "id": "provider-2", "name": "Anixo",    "url": "https://…/1",     "dubAvailable": true }
    ]
  }
}

providers[].url can be loaded directly in an iframe (srcdoc-free plain embed). If you request lang=dub but no provider has dub for that episode, effectiveLang becomes sub and dubMissing is true — exactly what the site shows.

Errors

Errors always use this envelope:

{
  "error": { "code": "invalid_token", "message": "The provided API token is not valid." }
}
CodeStatusMeaning
missing_token401No bearer token supplied.
invalid_token401Token does not exist.
token_revoked401Token (or its application) was revoked.
token_expired401OAuth access token expired (30 days). Get a new one.
insufficient_scope403OAuth token lacks the scope this endpoint needs.
rate_limited429More than 60 requests/minute. Retry after the `Retry-After` header.
invalid_params400Query parameter failed validation.
not_found404No anime exists with that id.
upstream_rate_limited429Upstream data provider is rate limiting us. Retry shortly.
upstream_error502Upstream data provider is unavailable.
auth_unavailable503Token verification temporarily unavailable. Retry.

Need a token? Create one on your account page.