maimai DX 곡 정보와 공개된 플레이어 기록을 앱에서 읽을 수 있는 API입니다.
maishift API로 앱에서 maimai DX의 곡 정보와 플레이어가 maishift에 공개한 기록을 읽을 수 있고, 플레이어가 허용하면 그 플레이어의 정보도 읽을 수 있습니다. 읽기 전용이며, 모든 요청에 API 키가 필요합니다.
이 페이지에서는 시작하는 방법과 모든 엔드포인트에 공통으로 적용되는 규칙을 설명합니다. 엔드포인트별 파라미터와 응답은 API 레퍼런스를 확인하세요.
X-API-Key 헤더로 키를 보냅니다.curl -H "X-API-Key: msk_…" "https://maimai.shiftpsh.com/api/v1/songs?region=intl"
키를 공개된 코드나 저장소에 올리지 마세요. 유출되었다면 설정 › 개발자에서 폐기하고 새로 발급하세요. 앱마다 사용 중인 키를 두 개까지 둘 수 있으니, 새 키로 바꾼 뒤 기존 키를 폐기하면 됩니다.
모바일, 데스크톱, 브라우저 앱은 키가 앱과 함께 사용자에게 배포되므로 추출될 수 있습니다. 이는 피할 수 없으며, 문제가 되지 않습니다. 키는 앱과 그 이용 한도를 식별할 뿐 플레이어를 보호하지 않으며, 플레이어의 로그인은 PKCE가 보호합니다. 추출된 키로 앱의 한도를 소모하는 경우가 생기면 키를 폐기하고 새로 발급하세요. 서버가 있는 앱이라면 키는 서버에 두세요.
| 엔드포인트 | 내용 |
|---|---|
GET /api/v1/songs |
지역의 모든 곡과 채보, 보면 상수 |
GET /api/v1/versions |
지역의 게임 버전 목록 |
GET /api/v1/profiles/{handle} |
플레이어의 공개 프로필 |
GET /api/v1/profiles/{handle}/records |
플레이어의 최신 기록 |
GET /api/v1/profiles/{handle}/snapshots |
플레이어의 이전 연동 목록 |
GET /api/v1/profiles/{handle}/snapshots/{snapshotId} |
특정 연동 시점의 기록 |
GET /api/v1/playlists/{playlistId} |
공개 플레이리스트 |
GET /api/v1/me |
앱을 허용한 플레이어 (OAuth) |
GET /api/v1/me/records |
그 플레이어의 최신 기록 |
GET /api/v1/me/snapshots |
그 플레이어의 이전 연동 목록 |
GET /api/v1/me/snapshots/{snapshotId} |
그 플레이어의 특정 연동 시점 기록 |
GET /api/v1/me/playlists |
비공개를 포함한 그 플레이어의 플레이리스트 |
GET /api/v1/me/playlists/{playlistId} |
그 플레이어의 플레이리스트 하나 |
GET /api/v1/stats/ratings |
활동 중인 플레이어의 레이팅 분포 |
GET /api/v1/stats/options |
플레이어들이 쓰는 게임 옵션 |
GET /api/v1/stats/plates |
레이팅별 버전 플레이트 보유 현황 |
GET /api/v1/stats/plate-difficulty |
레이팅 구간별 버전 플레이트 난이도 |
GET /api/v1/charts/{chartId}/distribution |
레이팅 구간별 채보의 달성률 분포 |
GET /api/v1/stats/levels/{internalLevel} |
같은 상수인 채보 전체의 달성률 분포 |
GET /api/v1/stats/level-charts |
한 레이팅 구간에서 같은 레벨 채보들의 분포 |
GET /api/v1/stats/clear-ratios |
레벨 진행도별로 클리어한 채보 |
전체 명세는 OpenAPI 3.1 형식으로 /api/v1/openapi.json에서도 받을 수 있으며, 클라이언트 코드 생성에 쓸 수 있습니다.
지역. 대부분의 엔드포인트는 region을 받습니다. intl은 국제판, jp는 일본판이며, na는 국제판 데이터를 북미 기체 기준으로 본 것으로, intl과 같은 데이터를 돌려주되 stats/plate-difficulty만 북미 기체의 플레이트 기준을 따릅니다. 두 지역 모두 북미 기체에서 고를 수 없는 곡에는 naUnavailable: true가 붙습니다.
ID. 곡과 채보의 ID는 지역마다 따로 매겨집니다. 같은 채보라도 intl과 jp에서 ID가 다릅니다.
버전. 버전은 정수 ID로 나타내며, 첫 maimai가 0입니다. 지역별 이름과 출시일은 GET /api/v1/versions에서 볼 수 있습니다. 한자는 버전 플레이트에 쓰이는 글자로, stats/plate-difficulty의 prefix와 같습니다.
| ID | 버전 | 한자 | jp 출시일 |
intl 출시일 |
|---|---|---|---|---|
| 0 | maimai | 舞 | 2012-07-11 | 2012-07-11 |
| 1 | maimai PLUS | 真 | 2012-12-13 | 2012-12-13 |
| 2 | GreeN | 超 | 2013-07-11 | 2013-07-11 |
| 3 | GreeN PLUS | 檄 | 2014-02-26 | 2014-02-26 |
| 4 | ORANGE | 橙 | 2014-09-18 | 2014-09-18 |
| 5 | ORANGE PLUS | 暁 | 2015-03-19 | 2015-03-19 |
| 6 | PiNK | 桃 | 2015-12-09 | 2015-12-09 |
| 7 | PiNK PLUS | 櫻 | 2016-06-30 | 2016-06-30 |
| 8 | MURASAKi | 紫 | 2016-12-15 | 2016-12-15 |
| 9 | MURASAKi PLUS | 菫 | 2017-06-22 | 2017-06-22 |
| 10 | MiLK | 白 | 2017-12-14 | 2017-12-14 |
| 11 | MiLK PLUS | 雪 | 2018-06-21 | 2018-06-21 |
| 12 | FiNALE | 輝 | 2018-12-13 | 2018-12-13 |
| 13 | でらっくす | 熊 | 2019-07-11 | 2020-01-15 |
| 14 | でらっくす PLUS | 華 | 2020-01-23 | 2020-07-29 |
| 15 | Splash | 爽 | 2020-09-17 | 2021-01-29 |
| 16 | Splash PLUS | 煌 | 2021-03-18 | 2021-07-30 |
| 17 | UNiVERSE | 宙 | 2021-09-16 | 2022-01-27 |
| 18 | UNiVERSE PLUS | 星 | 2022-03-24 | 2022-07-28 |
| 19 | FESTiVAL | 祭 | 2022-09-15 | 2023-01-19 |
| 20 | FESTiVAL PLUS | 祝 | 2023-03-23 | 2023-07-27 |
| 21 | BUDDiES | 双 | 2023-09-14 | 2024-01-18 |
| 22 | BUDDiES PLUS | 宴 | 2024-03-21 | 2024-07-25 |
| 23 | PRiSM | 鏡 | 2024-09-12 | 2025-01-16 |
| 24 | PRiSM PLUS | 彩 | 2025-03-13 | 2025-07-24 |
| 25 | CiRCLE | 丸 | 2025-09-18 | 2026-01-22 |
| 26 | CiRCLE PLUS | 廻 | 2026-03-19 | 2026-07-23 |
| 27 | MAGiCAL | — | 2026-09-17 | — |
intl과 jp는 같은 ID를 씁니다. DX(13)부터는 intl에 각 버전이 더 늦게 나오며, intl 출시일이 비어 있는 버전은 intl에는 아직 나오지 않은 버전입니다. DX 이전에는 국제판이 따로 없었으므로, 그 버전들은 두 지역 모두 일본판 출시일로 표시됩니다.
배율을 곱한 정수. 게임에서 소수로 표시되는 값은 배율을 곱해 정수로 보냅니다.
achievement는 10000배입니다. 1005000은 100.5000%입니다.internalLevel(보면 상수)은 10배입니다. 134는 13.4입니다.보면 상수. 채보마다 internalLevelSource로 상수의 출처를 알려 줍니다.
community: 현재 버전에 대해 커뮤니티가 공개한 값입니다.maishift: maishift가 정하거나 추정한 값입니다.estimated: 현재 버전의 값이 아직 알려지지 않아, 표기 난이도와 이전 버전 값으로 추정한 값입니다. 이 값으로 계산한 레이팅에는 ratingAccurate: false가 붙습니다.시각. 시각은 UTC 기준 ISO 8601 형식입니다. addedOn은 게임이 날짜를 세는 방식대로 일본 시간 기준 날짜(YYYY-MM-DD)입니다.
공개된 데이터만. 프로필과 최신 기록은 플레이어가 프로필을 공개한 경우에만 읽을 수 있습니다. 이전 연동 기록(스냅샷)은 이전 기록 공개도 켜져 있어야 합니다. 그렇지 않으면 403 not_public으로 응답합니다.
통계 엔드포인트는 여러 플레이어를 집계한 값만 돌려주며, 특정 플레이어의 기록은 담지 않습니다. 통계는 정해진 주기로 다시 계산됩니다(새 채보는 매시간, 나머지는 매일 또는 매주). 대부분의 응답에는 계산 시점이 computedAt으로 들어 있습니다.
레이팅 구간은 신곡 레이팅 대상이 다 채워지지 않은 플레이어의 빈자리를 채운 레이팅으로 나눕니다. 그래서 신곡을 15곡 다 플레이하지 않은 플레이어는 표시되는 rating보다 높은 구간에 들어갈 수 있습니다.
플레이어가 5명보다 적은 집단(레이팅 구간 등)은 값이 null이 되고 suppressed: true가 붙습니다. 그 정도 인원이면 수치가 집단이 아니라 특정 플레이어를 나타내기 때문입니다. 주로 레이팅 최상위 구간과 새로 추가된 채보에 해당합니다. 이는 수치 하나에서 특정 플레이어가 드러나지 않게 하는 안전장치일 뿐 보장은 아닙니다. 겹치는 집단의 수치를 조합하면 드러날 수도 있습니다. 통계로 특정 플레이어를 찾아내려 하지 마세요.
작은 집단을 나타내는 방식은 엔드포인트마다 조금 다릅니다. stats/ratings는 중앙값과 상위 백분위 기준값을 표시 없이 null로 두고, 히스토그램은 구간의 인원수까지 null로 둡니다. stats/clear-ratios는 빈 charts를 돌려줍니다. 인원수를 아직 모르는 레벨 구간은 크기와 관계없이 숨깁니다. stats/plate-difficulty는 여러 채보의 수치로 만든 추정치로, 이 규칙이 적용되지 않습니다.
오류는 HTTP 상태 코드와 함께 다음과 같은 본문으로 응답합니다.
{
"error": {
"code": "not_public",
"message": "This player's records are not public."
}
}
| 코드 | 상태 | 의미 |
|---|---|---|
missing_api_key |
401 | X-API-Key 헤더가 없습니다 |
invalid_api_key |
401 | 키가 올바르지 않거나 폐기되었습니다 |
app_suspended |
403 | 앱이 정지되었습니다 |
invalid_token |
401 | 액세스 토큰이 없거나, 만료되었거나, 폐기되었습니다 (/me만) |
insufficient_scope |
403 | 이 엔드포인트에 필요한 권한 범위가 토큰에 없습니다 (/me만) |
not_public |
403 | 플레이어가 공개하지 않은 데이터입니다 |
bad_request |
400 | 파라미터가 올바르지 않거나, 키를 URL에 넣었습니다 |
not_found |
404 | 해당하는 플레이어, 스냅샷, 플레이리스트, 채보가 없거나, 통계가 아직 계산되지 않았습니다 |
rate_limited |
429 | 요청이 너무 많습니다. 아래를 참고하세요 |
internal |
500 | 서버에 문제가 생겼습니다 |
키를 URL에 넣지 마세요. 쿼리 문자열에 키(api_key, apikey, key, access_token)가 들어간 요청은 거부됩니다.
/me 엔드포인트에서 토큰이 거부되면 WWW-Authenticate 헤더(RFC 6750)도 붙습니다. 토큰을 보내지 않았으면 Bearer scope="…", 올바르지 않은 토큰이면 Bearer error="invalid_token", 권한 범위가 부족하면 Bearer error="insufficient_scope", scope="…"입니다.
앱마다 120단위의 한도가 있으며, 초당 2단위(분당 120단위)씩 다시 채워집니다. 앱의 키들은 이 한도를 함께 씁니다. 요청마다 부하에 따라 아래 표의 단위만큼 소모됩니다.
| 엔드포인트 | 소모 |
|---|---|
versions |
1 |
profiles/{handle}/snapshots, me/snapshots, me/playlists |
2 |
profiles/{handle}, playlists/{playlistId}, me, me/playlists/{playlistId} |
5 |
stats/ratings, stats/options, stats/plates, stats/levels/{internalLevel}, charts/{chartId}/distribution |
6 |
songs, profiles/{handle}/records, profiles/{handle}/snapshots/{snapshotId}, me/records, me/snapshots/{snapshotId} |
10 |
stats/level-charts, stats/clear-ratios |
15 |
stats/plate-difficulty |
30 |
/me 엔드포인트 요청은 앱을 허용한 플레이어마다 따로 있는 한도를 씁니다. 크기는 앱의 한도와 같으므로, 플레이어가 늘어날수록 쓸 수 있는 양도 늘어납니다. 토큰 엔드포인트와 폐기 엔드포인트는 앱의 한도에서 각각 1단위를 씁니다.
키가 받아들여지면 /api/v1의 모든 응답에 오류 응답을 포함해 X-RateLimit-Limit와 X-RateLimit-Remaining 헤더가 붙습니다(app_suspended 제외). /me 엔드포인트에서는 플레이어의 한도를 나타내지만, 거부된 액세스 토큰은 앱의 한도에서 1단위를 쓰고 앱의 한도를 나타냅니다. 한도를 넘으면 429와 함께, 몇 초 뒤에 다시 시도하면 되는지 알려 주는 Retry-After 헤더로 응답합니다. 토큰 엔드포인트와 폐기 엔드포인트는 slow_down과 함께 Retry-After만 보냅니다.
곡 목록은 하루에 많아야 몇 번 바뀝니다. 요청마다 받아 오지 말고 캐시해 두세요.
API를 이용하면 개발자 약관에 동의한 것으로 봅니다. 요약하면 다음과 같습니다.
플레이어가 허용하면, 앱에서 공개하지 않은 정보까지 포함해 그 플레이어의 정보를 읽을 수 있습니다. maishift는 OAuth 2.0 인가 코드 방식을 쓰며, 모든 앱에 PKCE가 필요합니다.
OAuth는 내 maishift 프로필이 공개되어 있을 때만 쓸 수 있습니다. 플레이어는 앱을 연결하기 전에 누가 만든 앱인지 확인할 수 있어야 하기 때문입니다. 공개하기 전에는 동의 화면에서 플레이어에게 이 앱을 연결할 수 없다고 안내합니다.
1. 리디렉션 URI 등록. 설정 › 개발자에서 플레이어가 돌아올 URI를 모두 추가하세요. https 주소, 로컬 테스트용 localhost, 127.0.0.1, [::1]의 http 주소, 또는 com.example.app:/callback처럼 역방향 도메인 형식의 앱 스킴을 쓸 수 있습니다. 프래그먼트(#)나 사용자 이름은 넣을 수 없고, maishift 자신을 가리킬 수 없으며, 512자까지 쓸 수 있습니다. 앱마다 10개까지 등록할 수 있으며, 요청에는 이 중 하나와 정확히 같은 URI를 써야 합니다.
2. 플레이어를 maishift로 보내기. A–Z a–z 0–9 - . _ ~로 이루어진 43~128자의 임의의 code_verifier를 만들어 보관하세요. code_challenge는 이 값의 SHA-256을 패딩 없는 base64url로 인코딩한 것입니다. 그리고 설정 › 개발자의 클라이언트 ID를 넣어 다음 주소를 엽니다. 값은 URL 인코딩해야 합니다(범위 사이의 공백은 %20). 줄바꿈은 읽기 쉽게 하려고 넣은 것입니다.
https://maimai.shiftpsh.com/oauth/authorize
?response_type=code
&client_id=msc_…
&redirect_uri=https://example.com/callback
&scope=records offline.access
&state=…
&code_challenge=…
&code_challenge_method=S256
플레이어는 로그인한 뒤 앱이 요청하는 권한을 확인합니다. 허용하면 code와 보낸 state가, 거부하면 error=access_denied가 붙어 리디렉션 URI로 돌아옵니다. state가 보낸 값과 같은지 꼭 확인하세요.
요청이 잘못된 경우에도 error, error_description, 보낸 state가 붙어 돌아옵니다. invalid_request(PKCE가 없거나 형식이 잘못되었거나 S256이 아닌 방식), invalid_scope(알 수 없는 범위), unsupported_response_type이 있습니다. 다음 경우에는 돌아가지 않고 플레이어에게 maishift의 오류 화면이 보입니다. 클라이언트 ID를 알 수 없을 때, 등록하지 않은 리디렉션 URI일 때, 앱이 정지되었을 때, 내 프로필이 공개되어 있지 않을 때, 그리고 플레이어가 공용 컴퓨터로 로그인했거나 제재된 계정일 때입니다.
3. 코드를 토큰으로 교환. 10분 안에 교환해야 하며, 코드는 한 번만 쓸 수 있습니다.
curl -X POST https://maimai.shiftpsh.com/api/oauth/token \
-H "X-API-Key: msk_…" \
-d grant_type=authorization_code \
-d code=… \
-d redirect_uri=https://example.com/callback \
-d code_verifier=…
{
"access_token": "msa_…",
"token_type": "Bearer",
"expires_in": 7200,
"refresh_token": "msr_…",
"scope": "profile records offline.access"
}
토큰 엔드포인트는 form 형식과 JSON 형식을 모두 받습니다. API 키로 앱을 알 수 있으므로 client_id는 보내지 않아도 됩니다. redirect_uri는 /oauth/authorize에 보낸 것과 같아야 합니다. 교환에 실패해도 코드는 다시 쓸 수 없습니다.
토큰 엔드포인트와 폐기 엔드포인트는 OAuth 방식(RFC 6749)으로 { "error": "…", "error_description": "…" } 형태의 오류를 돌려줍니다.
error |
상태 | 의미 |
|---|---|---|
invalid_client |
401 | API 키가 없거나, 올바르지 않거나, 폐기되었거나, 앱이 정지되었습니다 |
invalid_grant |
400 | 코드나 리프레시 토큰이 올바르지 않거나, 만료되었거나, 이미 쓰였거나, 다른 앱의 것입니다. PKCE나 redirect_uri가 맞지 않거나, 플레이어나 앱이 더 이상 로그인할 수 없는 경우도 포함됩니다 |
invalid_request |
400 | 필요한 값이 빠졌습니다 |
invalid_scope |
400 | 갱신할 때 알 수 없는 범위나 허용받은 것보다 넓은 범위를 요청했습니다 |
unsupported_grant_type |
400 | grant_type이 authorization_code도 refresh_token도 아닙니다 |
slow_down |
429 | 앱의 한도를 다 썼습니다. Retry-After만큼 기다리세요 |
server_error |
500 | 서버에 문제가 생겼습니다 |
4. /me 엔드포인트 호출. API 키와 액세스 토큰을 함께 보냅니다.
curl -H "X-API-Key: msk_…" -H "Authorization: Bearer msa_…" \
https://maimai.shiftpsh.com/api/v1/me/records
| 범위 | 앱이 할 수 있는 것 |
|---|---|
profile |
플레이어가 누구인지 확인(/me). 항상 포함됩니다. |
records |
최신 기록 읽기. 프로필이 비공개여도 읽을 수 있습니다. |
history |
이전 연동 기록 읽기. 이전 기록이 비공개여도 읽을 수 있습니다. 가장 최근 연동이 곧 최신 기록이므로 records도 포함됩니다. |
playlists |
비공개를 포함한 플레이리스트 읽기 |
offline.access |
리프레시 토큰 발급. 2시간이 지나도 계속 접근할 수 있습니다. |
앱에 필요한 범위만 요청하세요. 플레이어는 동의하기 전에 목록을 봅니다. 권한을 받아도 친구 코드는 플레이어 본인의 공개 설정을 따릅니다.
액세스 토큰은 2시간 동안 유효합니다. offline.access가 없으면, 만료된 뒤에는 플레이어에게 다시 허용을 받아야 합니다.
offline.access가 있으면 토큰 응답에 리프레시 토큰도 들어 있습니다. 90일 동안 쓰지 않으면 만료됩니다. 쓸 때마다 새 리프레시 토큰이 발급되고 이전 토큰은 더 이상 쓸 수 없습니다. 이전 토큰이 다시 쓰이면 유출된 것으로 보고 그 로그인을 끝냅니다. 그 로그인에서 발급된 토큰은 모두 쓸 수 없게 되고, 플레이어는 그 기기에서 다시 허용해야 합니다. 같은 토큰으로 두 번 갱신하면 재사용으로 처리되므로, 갱신은 한 번에 하나씩 하세요. 로그인은 기기마다 따로 관리되므로, 두 기기에서 로그인한 플레이어라면 다른 기기의 로그인은 그대로 유지됩니다. 갱신할 때 scope를 보내 허용받은 범위를 그대로 두거나 좁힐 수 있으며, 넓힐 수는 없습니다. offline.access를 빼고 좁히면 새 리프레시 토큰이 발급되지 않으므로, 그 로그인은 액세스 토큰이 만료될 때 끝납니다.
curl -X POST https://maimai.shiftpsh.com/api/oauth/token \
-H "X-API-Key: msk_…" \
-d grant_type=refresh_token \
-d refresh_token=msr_…
플레이어가 앱에서 로그아웃하면, 두 토큰 중 하나를 API 키와 함께 POST /api/oauth/revoke(token=…)로 보내세요. 그 기기의 로그인이 끝나고, 남은 로그인이 없으면 앱의 연결도 해제됩니다. 플레이어가 설정 › 연결된 앱에서 연결을 해제하면 모든 로그인이 끝납니다. 어느 쪽이든 토큰은 더 이상 쓸 수 없습니다. API 요청에는 401 invalid_token, 갱신에는 invalid_grant로 응답합니다. 폐기 엔드포인트는 어떤 토큰을 보내든 200으로 응답하므로, 어떤 토큰이 있는지는 알려 주지 않습니다.
/me는 playerId를 돌려줍니다. 같은 플레이어라면 내 앱에서는 늘 같고, 다른 앱에서는 다릅니다. 다시 찾아온 플레이어를 알아볼 때는 바뀔 수 있는 핸들 대신 이 값을 저장하세요.
난이도의 소수점 아래 부분이 흐릿하게 표시되는 곡들은 현재 버전에 알려진 상수가 없는 곡들이며, 표기 난이도 및 이전 버전 상수들을 토대로 추정한 정확하지 않을 수 있는 값입니다. 보면 상수는 maimai譜面定数ちほー 및 新・CYCLES FUNの寝言 프로젝트로부터 가져왔으며, 알려져 있지 않은 상수 일부는 사용자 데이터를 기반으로 자체 추정합니다.
본 사이트는 개인 성과 기록 및 추적을 위해 만든 maimai DX의 팬 사이트이며, 사이트 내에 사용된 게임 관련 컨텐츠의 저작권은 SEGA 및 각 소유자들에게 있습니다.