An unofficial specification for MyAnimeList.net's long undocumented APIs. GPLv3 Licensed.
For starters, I've only been a MyAnimeList user for a short period of time. Nevertheless, as far as I know, MyAnimeList was and still is the largest anime information database website, which also happens to be infamous to the developers for its unstable APIs.
This document intends to create an up-to-date specification for the private (open-beta) APIs that MyAnimeList is using in its official mobile apps. The goal is to encourage the developments of third-party and (particularly) open source applications.
As mentioned, the specifications here (mostly) came from the analysis of MyAnimeList's official Android app and some popular third-party applications. Note because this document uses MAL's private (or at least unpublished) APIs, everything is subject to change.
This document only discovers the anime-related APIs. But feel free to create a pull request/issue if you have something related that you want to share or change.
- MyAnimeList Unofficial API Specification
API Endpoint: https://api.myanimelist.net/v2
Client Identifier (from MAL's official Android app): 6114d00ca681b7701d1e15fe11a4987e
Example Request
GET /v2/anime/search?status=not_yet_aired&limit=1&offset=0&fields=alternative_titles HTTP/1.1
Host: api.myanimelist.net
Accept: application/json
User-Agent: NineAnimator/2 CFNetwork/976 Darwin/18.2.0
Authorization: Bearer <OAuth2 Token>
X-MAL-Client-ID: 6114d00ca681b7701d1e15fe11a4987e
Note: Always include the
X-MAL-Client-ID
header. Currently the only known client id is that of the MAL's official Android app.
If the request is intended for mutations (e.g. modify user library entries),
the request body is url-form encoded with content type: application/x-www-form-urlencoded
.
The responses are formatted in json with content type
application/json; charset=UTF-8
.
If the request succeeded, the server returns 200
with a data
object at the
root of its response JSON object.
Example Response
HTTP/1.1 200 OK
{
"data": [
{
"node": {
"id": 34134,
"title": "One Punch Man Season 2",
"main_picture": {
"medium": "https:\/\/myanimelist.cdn-dena.com\/images\/anime\/1797\/93459.jpg",
"large": "https:\/\/myanimelist.cdn-dena.com\/images\/anime\/1797\/93459l.jpg"
},
"alternative_titles": {
"synonyms": [ "One Punch-Man 2", "One-Punch Man 2","OPM 2" ],
"en": "",
"ja": "\u30ef\u30f3\u30d1\u30f3\u30de\u30f3 2"
}
}
}
],
"paging": {
"next": "https:\/\/api.myanimelist.net\/v2\/anime\/search?offset=2&status=not_yet_aired&limit=2&fields=alternative_titles"
}
}
If the request failed, the server respond with an HTTP error status and returns an error message.
Example Response
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=UTF-8
{ "message": "invalid q", "error": "bad_request" }
OAuth2 is used in MAL's authentication scheme. Two tokens will be returned
upon a successful authentication: the access_token
and the refresh_token
.
Once authenticated, all of your requests should include the header
Authentication
with value Bearer <access_token>
.
- Authenticate with the account's username and password.
Send a url-form encoded POST
request to https://api.myanimelist.net/v2/auth/token
with the following parameters:
Parameter | Value |
---|---|
client_id |
String: 6114d00ca681b7701d1e15fe11a4987e |
grant_type |
String: password |
password |
String: The account's password |
username |
String: The account's username |
Example Request
POST /v2/auth/token HTTP/1.1
Host: api.myanimelist.net
Accept: application/json
User-Agent: NineAnimator/2 CFNetwork/976 Darwin/18.2.0
X-MAL-Client-ID: 6114d00ca681b7701d1e15fe11a4987e
Content-Type: application/x-www-form-urlencoded
Content-Length: 112
client_id=6114d00ca681b7701d1e15fe11a4987e&grant_type=password&password=xxxxxx-yyyyyy-zzzzzz&username=abcdefghij
- Authenticate (or I guess re-authenticate) with a previously returned
refresh_token
Send a url-form encoded POST
request to https://myanimelist.net/v1/oauth2/token
(note this url is different from the one used in the Password Grant).
Parameter | Value |
---|---|
client_id |
String: 6114d00ca681b7701d1e15fe11a4987e |
grant_type |
String: refresh_token |
refresh_token |
String: The refresh token previously received |
Example Request
POST /v1/auth/token HTTP/1.1
Host: myanimelist.net
Accept: application/json
User-Agent: NineAnimator/2 CFNetwork/976 Darwin/18.2.0
X-MAL-Client-ID: 6114d00ca681b7701d1e15fe11a4987e
Content-Type: application/x-www-form-urlencoded
Content-Length: 88
client_id=6114d00ca681b7701d1e15fe11a4987e&grant_type=refresh_token&refresh_token=xxxxxx
- Upon a successful authentication, the server responds
200 OK
and a JSON object with the following entries:
Entry | Value |
---|---|
token_type |
String: Bearer |
expires_in |
Integer: The lifespan of the responded access token in seconds, after which the client needs to be reauthenticated. |
access_token |
String: The access token that should be included in further requests from this client. |
refresh_token |
String: The refresh token that can be used to re-authenticate the client with Refresh Token Grant |
You should persist the refresh_token
in a secure location. When the access_token
expires after expires_in
seconds, use the refresh_token
to re-authenticate the
session. See Refresh Token Grant.
Example Response
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
{
"token_type": "Bearer",
"expires_in": 3600,
"access_token": "xxxx",
"refresh_token": "xxxx"
}
Further requests should be made with the Authorization
header. See Making Requests.
- Upon an unsuccessful authentication, the server responds an error HTTP status code and a JSON object with the following entries:
Entry | Value |
---|---|
error |
String: The type of the error |
message |
String: A human-readable description of the error. |
(Optional) hint |
String: An optional message about how the error occurred. |
Example Response
HTTP/2 401
Content-Type: application/json; charset=UTF-8
{
"error": "invalid_request",
"message": "The refresh token is invalid.",
"hint": "Cannot decrypt the refresh token"
}
Resource requests are made to the endpoint https://api.myanimelist.net/v2
.
- Always include the
X-MAL-Client-ID
header, or you will get an unauthorized error. See Making Requests - The old endpoint
https://api.myanimelist.net/v0.21
still works. - There happens to be a different endpoint
https://api.myanimelist.net/v0.8
. But when you make a mutational request to this endpoint, the server will responds an outdated app error.
Set the following GET parameters in your request url to limit the number of resources included in the response:
Parameter | Value |
---|---|
limit |
The maximum number of elements to be contained in the data section. |
offset |
The page number that the client is requesting. |
Example Request URL with Paging
https://api.myanimelist.net/v2/anime?q=An+Anime+Title&limit=5&offset=0
When the response is paged, the JSON object returned also includes a paging
section. The next
entry in the paging
section includes the full URL to the
next page.
Exampled Response with Paging
{
"data": [ ... ],
"paging": {
"next": "https:\/\/api.myanimelist.net\/v2\/anime?offset=5&q=An+Anime+Title&limit=5"
}
}
When you make a request to a specific resource, you can set the fields
parameter in your URL query to limit the fields responded by the server.
- Includes a
fields
parameter whenever you can. This decreases the load time and (perhaps) decreases the load on MAL's server as well. - The value of the
fields
parameter is a comma seperated list corresponding to the JSON keys that will be returned in the response. - If you want to includes fields in the sub-sections of the response objects,
use
<Subsection Name>{<Subsection Entry 1>, <Subsection Entry 2>, <Subsection Entry 3>...}
.
Example Request URL with Fields parameter
https://api.myanimelist.net/v2/anime?q=An+Anime+Title&fields=alternative_titles,media_type,my_list_status{start_date,finish_date}
A list of known request paths and response objects.
- Paths are relative to the API endpoint:
https://api.myanimelist.net/v2
- Responses are JSON encoded objects with utf8 encodings.
- Request Path:
/users/<user id>
- Method:
GET
This operation doesn't require any parameters. Note @me
can be used in place of <user id>
. See Referring the Current User.
An UserObject
.
Example Request
GET https://api.myanimelist.net/v2/users/@me HTTP/2.0
authorization: Bearer <bearer token>
Example Response
HTTP/2.0 200
content-type: application/json; charset=UTF-8
{"id":1234567,"name":"username","location":"some location","joined_at":"2010-01-01T01:11:11+00:00"}
- Request Path:
/anime/<anime id>
- Method:
GET
This operation supports the use of the fields
parameter.
An AnimeObject
.
- Request Path:
/anime
- Method:
GET
, response supports paging
Parameter | Value |
---|---|
q |
String: Keywords to be used as search parameters |
- Root Response Object:
Object
data
:Array<AnimeObject>
- A list ofAnimeObject
. The results from the search.
- Request Path:
/users/<User Identifier>/animelist
- Method:
GET
, response supports paging
Parameter | Value |
---|---|
sort |
SortingMethodEnum: The method of sorting the responsing entries. |
status |
ListStatusEnum: Filter the status of the entries. |
- Root Response Object:
Object
data
:Array<AnimeObject>
- A list ofAnimeObject
.
- Request Path:
/anime/<Anime Identifier>/my_list_status
- Method: Either
PATCH
orPUT
The parameters are url-form encoded in the body of the request. The parameter keys resembles those found in MyListStatusObject
.
Parameter | Value |
---|---|
num_watched_episodes |
An integer denoting the number of episodes watched. Note this is different from the key in MyListStatusObject (#1). |
status |
ListStatusEnum |
score |
An integer representing the user's rating. Value ranging from 1 to 10. Set this value to 0 to remove the user's rating. |
start_date |
A year-month-day string that indicates when the user started watching the entry (eg. 2020-1-1 ). |
finish_date |
A year-month-day string that indicates when the user finished watching the entry (eg. 2020-1-1 ). |
- Root Response Object:
MyListStatusObject
- The updated status object
Example Request
PATCH https://api.myanimelist.net/v2/anime/34881/my_list_status HTTP/2.0
authorization: Bearer <bearer token>
content-type: application/x-www-form-urlencoded
finish_date=2020-01-1&start_date=2020-02-01&num_watched_episodes=2
Example Response
HTTP/2.0 200
content-type: application/json; charset=UTF-8
{"status":"on_hold","score":0,"num_episodes_watched":2,"is_rewatching":false,"updated_at":"2020-01-1T20:50:00+00:00","start_date":"2020-01-01","finish_date":"2020-02-1","priority":0,"num_times_rewatched":0,"rewatch_value":0,"tags":[],"comments":""}
- Request Path:
/anime/<Anime Identifier>/my_list_status
- Method:
DELETE
No parameters needed for this operation.
An empty array []
for a successful removal.
Example Request
DELETE https://api.myanimelist.net/v2/anime/39590/my_list_status HTTP/2.0
authorization: Bearer <bearer token>
Example Response
HTTP/2.0 200
content-type: application/json; charset=UTF-8
[]
You can reference to the currently authenticated user with the
@me
placeholder in the request URL.
Example Request URL
https://api.myanimelist.net/v2/users/@me/animelist
An AnimeObject
represents an anime in MAL's database.
AnimeObject
:Object
node
:Object
alternative_titles
:AlternativeTitlesObject
average_episode_duration
: Int - The average duration (in seconds) of the episodes.broadcast
:BroadcastObject
created_at
:Date
end_date
:CalendarDate
- The date at which the anime ended.genres
:Array
<GenreObject
> - Genres of the anime.id
:Int
- The identifier of this media on MyAnimeList.main_picture
:PictureObject
- The poster artwork of the anime.mean
:Double
- The mean score of this media on MyAnimeList.media_type
:String
- The type of this media (tv
,ova
,movie
,special
,ona
,music
,unknown
).nsfw
:String
- The NSFW state for this media (white
,gray
,black
).num_episodes
:Int
- The number of episodes in this anime.num_favorites
:Int
- The number of users that added this media to their favorites.num_list_users
:Int
- The number of uses that added this media to their lists.num_scoring_users
:Int
- (?) The number of users that voted for the scores.popularity
:Int
- The popularity rankings of this anime.rank
:Int
- The rankings of this anime.start_date
:CalendarDate
- The date at which the anime started.start_season
:SeasonObject
- The season at which the anime started broadcasting.status
:String
- An enumeration representing the broadcasting status of the anime (finished_airing
,currently_airing
,not_yet_aired
).synopsis
:String
- The synopsis of the anime.source
:String
- The original work that inspired this anime (original
,manga
,4_koma_manga
,web_manga
,digital_manga
,novel
,light_novel
,visual_novel
,game
,card_game
,book
,picture_book
,radio
,music
,other
).studio
:AnimeStudioObject
title
:String
- The canonical (?) title of the anime.updated_at
:Date
- The last time that the information is updated on MyAnimeList.my_list_status
:MyListStatusObject
background
:String
- Background story of the animerelated_anime
:Array<AnimeObject>
- A list of anime related to this animerating
:String
- The rating of this anime (g
All Ages,pg
Children,pg_13
Teens 13 and Older,r
17+ (violence & profanity),r+
Profanity & Mild Nudity,rx
Hentai).
The set of titles and synonyms of the anime.
AlternativeTitlesObject
:Object
en
:String
- The English title of the mediaja
:String
- The original (native) name of the mediasynonyms
:Array<String>
- A list of synonyms of the media
The studio of the anime.
AnimeStudioObject
:Object
id
:Integer
- The id of the studioname
:String
- The name of the studio
The broadcasting schedule of the anime.
BroadcastObject
:Object
day_of_the_week
:String
- A lower-cased string of the day that the media is released in a week. E.g.thursday
start_time
:String
- The time of the broadcast. E.g.19:30
A simple date formatted as yyyy-mm-dd
(e.g. 2007-02-08
).
CalendarDate
:String
A specific time.
Date
:String
- AISO 8601
formatted time string.
The genre of the anime.
GenreObject
:Object
id
:Integer
- The id of the genrename
:String
- The name of the genre (Action
,Adventure
,Cars
,Comedy
,Dementia
,Demons
,Drama
,Ecchi
,Fantasy
,Game
,Harem
,Hentai
,Historical
,Horror
,Josei
,Kids
,Magic
,Martial Arts
,Mecha
,Military
,Music
,Mystery
,Parody
,Police
,Psychological
,Romance
,Samurai
,School
,Sci-Fi
,Seinen
,Shoujo
,Shoujo Ai
,Shounen
,Shounen Ai
,Slice of Life
,Space
,Sports
,Super Power
,Supernatural
,Thriller
,Vampire
,Yaoi
,Yuri
)
A set of pictures.
PictureObject
:Object
large
:String
- An absulute URL to the high(er) resolution picturemedium
:String
- An absulute URL to the medium resolution picture
The method of sorting the entries in the response lists.
SortingMethodEnum
:String
- One ofanime_title
,list_score
,list_updated_at
,anime_start_date
Representing a season in a year.
SeasonObject
:Object
season
:String
- The season in a year (E.g.fall
).year
:Int
- The four digits integer representation of a year (E.g.2002
).
The list status of the user.
ListStatusEnum
:String
- One ofwatching
,completed
,on_hold
,dropped
,plan_to_watch
The library entry.
MyListStatusObject
:Object
comments
:String
is_rewatching
:Bool
num_episodes_watched
:Int
num_times_rewatched
:Int
priority
:Int
rewatch_value
:Int
score
:Double
status
:ListStatusEnum
tags
: ?updated_at
:Date
UserObject
:Object
id
:Int
An integer id of the user.name
:String
location
:String
joined_at
:Date