Minecraft:Mojang API: Difference between revisions
More actions
Remove broken links to missing pages |
Sync: updated from Minecraft Tag: Manual revert |
||
| (17 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
This page provides documentation for '''API''' endpoints provided by Mojang Studios which allows user to query player data and make changes programmatically. | This page provides documentation for '''API''' endpoints provided by [[Minecraft:Mojang Studios]] which allows user to query player data and make changes programmatically. | ||
Most of the API has a per-IP ratelimit of 200 requests per 2 minutes. For IPv6, the ratelimits are bucketed by /56 subnet. Some endpoints have different ratelimits. | Most of the API has a per-IP ratelimit of 200 requests per 2 minutes. For IPv6, the ratelimits are bucketed by /56 subnet. Some endpoints have different ratelimits. | ||
| Line 102: | Line 102: | ||
These endpoints do not need an access token, and some endpoints can query registered account that do not own the game. | These endpoints do not need an access token, and some endpoints can query registered account that do not own the game. | ||
=== Query player's | === Query player's UUID === | ||
; Input | ; Input | ||
| Line 135: | Line 135: | ||
* HTTP 404 is returned if no player with such name exists. | * HTTP 404 is returned if no player with such name exists. | ||
=== Query player's | === Query player's username === | ||
; Input | ; Input | ||
| Line 144: | Line 144: | ||
; Response | ; Response | ||
The same as [[#Query player's | The same as [[#Query player's UUID]]. | ||
=== Query player UUIDs in batch === | === Query player UUIDs in batch === | ||
| Line 168: | Line 168: | ||
; Example | ; Example | ||
Request with payload <code><nowiki>["jeb_","notch"]</nowiki></code>. | Request with payload <code><nowiki>["jeb_","notch"]</nowiki></code>. | ||
<syntaxhighlight lang="json"> | <syntaxhighlight lang="json">[ | ||
[ | |||
{ | { | ||
"id": "853c80ef3c3749fdaa49938b674adae6", | "id": "853c80ef3c3749fdaa49938b674adae6", | ||
| Line 178: | Line 177: | ||
"name": "Notch" | "name": "Notch" | ||
} | } | ||
] | ]</syntaxhighlight> | ||
</syntaxhighlight> | |||
{| class="wikitable mw-collapsible mw-collapsed" | {| class="wikitable mw-collapsible mw-collapsed" | ||
| Line 201: | Line 199: | ||
; Input | ; Input | ||
Player UUID and whether the request is signed. | Player UUID and whether the request is [[Minecraft:wikipedia:Digital signature|signed]]. | ||
; Request (GET) | ; Request (GET) | ||
| Line 217: | Line 215: | ||
*** {{Nbt|string|name}}: Name of the property. For now, the only property that exists is <code>textures</code>. | *** {{Nbt|string|name}}: Name of the property. For now, the only property that exists is <code>textures</code>. | ||
*** {{Nbt|string|signature}}: Signature signed with <samp>Yggdrasil</samp> private key as Base64 string, only exists when <code>unsigned=false</code>. | *** {{Nbt|string|signature}}: Signature signed with <samp>Yggdrasil</samp> private key as Base64 string, only exists when <code>unsigned=false</code>. | ||
*** {{Nbt|string|value}}: Base64 string with all player textures (skin and cape). The decoded string includes: | *** {{Nbt|string|value}}: [[Minecraft:wikipedia:Base64|Base64]] string with all player textures (skin and cape). The decoded string includes: | ||
**** {{Nbt|compound}} Texture object. | **** {{Nbt|compound}} Texture object. | ||
***** {{Nbt|int|timestamp}}: Unix time in milliseconds the texture is accessed. | ***** {{Nbt|int|timestamp}}: [[Minecraft:wikipedia:Unix time|Unix time]] in milliseconds the texture is accessed. | ||
***** {{Nbt|string|profileId}}: Player's UUID without dashes. | ***** {{Nbt|string|profileId}}: Player's UUID without dashes. | ||
***** {{Nbt|string|profileName}}: Player name. | ***** {{Nbt|string|profileName}}: Player name. | ||
***** {{Nbt|boolean|signatureRequired}}: Only exists when <code>unsigned=false</code>. | ***** {{Nbt|boolean|signatureRequired}}: Only exists when <code>unsigned=false</code>. | ||
***** {{Nbt|compound|textures}}: Texture. | ***** {{Nbt|compound|textures}}: Texture. | ||
****** {{Nbt|compound|SKIN}}: Skin texture. This does not exist if the player does not have a custom skin. | ****** {{Nbt|compound|SKIN}}: [[Minecraft:Skin]] texture. This does not exist if the player does not have a custom skin. | ||
******* {{Nbt|string|url}}: URL to the skin texture. | ******* {{Nbt|string|url}}: URL to the skin texture. | ||
******* {{Nbt|compound|metadata}}: Optional. Metadata for the skin. | ******* {{Nbt|compound|metadata}}: Optional. Metadata for the skin. | ||
******** {{Nbt|string|model}}: <code>slim</code>. Only exists when skin model is <samp>Alex</samp>. When skin model is <samp>Steve</samp>, this metadata does not exist. | ******** {{Nbt|string|model}}: <code>slim</code>. Only exists when skin model is <samp>Alex</samp>. When skin model is <samp>Steve</samp>, this metadata does not exist. | ||
****** {{Nbt|compound|CAPE}}: Cape texture. If the player does not have a cape, this does not exist. | ****** {{Nbt|compound|CAPE}}: [[Minecraft:Cape]] texture. If the player does not have a cape, this does not exist. | ||
******* {{Nbt|string|url}}: URL to the cape texture. | ******* {{Nbt|string|url}}: URL to the cape texture. | ||
</div> | </div> | ||
| Line 429: | Line 427: | ||
* {{Nbt|compound}} Root tag | * {{Nbt|compound}} Root tag | ||
** {{Nbt|compound|privileges}}: Player's privileges | ** {{Nbt|compound|privileges}}: Player's privileges | ||
*** {{Nbt|compound|onlineChat}}: Privilege of accepting chat messages | *** {{Nbt|compound|onlineChat}}: Privilege of accepting chat messages. | ||
**** {{Nbt|boolean|enable}} | **** {{Nbt|boolean|enable}} | ||
*** {{Nbt|compound|multiplayerServer}}: Privilege of joining servers. | *** {{Nbt|compound|multiplayerServer}}: Privilege of joining servers. | ||
| Line 441: | Line 439: | ||
** {{Nbt|compound|profanityFilterPreferences}}: Profanity filter settings. | ** {{Nbt|compound|profanityFilterPreferences}}: Profanity filter settings. | ||
*** {{Nbt|boolean|profanityFilterOn}}: If Realms profanity filter is on. | *** {{Nbt|boolean|profanityFilterOn}}: If Realms profanity filter is on. | ||
** {{Nbt|compound|friendsPreferences}}: | |||
*** {{Nbt|string|friends}}: Whether the player has enabled the friend list feature. Values: <code>ENABLED</code>, <code>DISABLED</code>. | |||
*** {{Nbt|string|acceptInvites}}: Whether the player allows friend requests. Values: <code>ENABLED</code>, <code>DISABLED</code>. | |||
** {{Nbt|compound|chatPreferences}}: | |||
*** {{Nbt|string|textCommunication}}: Serves the same purpose as {{Nbt|compound|onlineChat}}, with the addition of the <code>FRIENDS_ONLY</code> value for hiding chat messages from non-friends. {{Nbt|compound|onlineChat}} remains as a form of backwards compatibility to versions which do not query {{Nbt|string|textCommunication}}. Values: <code>ENABLED</code>, <code>DISABLED</code>, <code>FRIENDS_ONLY</code>. | |||
** {{Nbt|compound|banStatus}}: Player's ban status | ** {{Nbt|compound|banStatus}}: Player's ban status | ||
*** {{Nbt|compound|bannedScopes}}: Scope in which the player is banned. | *** {{Nbt|compound|bannedScopes}}: Scope in which the player is banned. | ||
| Line 453: | Line 456: | ||
; Payload | ; Payload | ||
<div class="treeview"> | A JSON array containing the desired keys to update, right now, only orofanityFilterPreferences and friendsPreferences can be updated<div class="treeview"> | ||
* '''Example''' | |||
* {{Nbt|compound}} Root tag | * {{Nbt|compound}} Root tag | ||
** {{Nbt|compound|profanityFilterPreferences}}: Realms profanity filter options. | ** {{Nbt|compound|profanityFilterPreferences}}: Realms profanity filter options. | ||
*** {{Nbt|boolean|profanityFilterOn}}: If filter is on. | *** {{Nbt|boolean|profanityFilterOn}}: If filter is on. | ||
**{{Nbt|compound|friendsPreferences}}: Friends list options. | |||
***{{Nbt|compound|friends}}: Whether the player has enabled the friend list feature. Values: <code>ENABLED</code>, <code>DISABLED</code> | |||
***{{Nbt|compound|acceptInvites}}: Whether the player allows friend requests. Values: <code>ENABLED</code>, <code>DISABLED</code>. | |||
</div> | </div> | ||
| Line 726: | Line 733: | ||
** {{Nbt|list|playerCertificateKeys}}: A list of public keys used for verifying player public keys, e.g. from <code><nowiki>https://api.minecraftservices.com/player/certificates</nowiki></code>. A player certificate is considered valid by the client iff signed by any of these keys. | ** {{Nbt|list|playerCertificateKeys}}: A list of public keys used for verifying player public keys, e.g. from <code><nowiki>https://api.minecraftservices.com/player/certificates</nowiki></code>. A player certificate is considered valid by the client iff signed by any of these keys. | ||
*** {{Nbt|string}}: Base64-encoded DER public key. | *** {{Nbt|string}}: Base64-encoded DER public key. | ||
** {{Nbt|list|authenticationKeys}}: A list of public keys used for verifying authentication tokens, like the JWT access token used in the <code>Authorization</code> Header in most requests to the Minecraft API. | |||
*** {{Nbt|string}}: Base64-encoded DER public key. | |||
</div> | |||
== Friends list == | |||
The friends list was introduced in [[Minecraft:Java Edition 26.2 Snapshot 7|Minecraft Java Edition 26.2 Snapshot 7]]. The API and client behavior are as such unstable and likely to change. | |||
These endpoints are located on <code><nowiki>https://api.minecraftservices.com</nowiki></code>, and all require the <code>Authorization</code> header to be provided in the request with a value of <samp>Bearer <''Minecraft access token''></samp>. An HTTP 401 error is returned if the token is missing or invalid. | |||
=== Get list of friends and friend requests === | |||
; Request (GET) | |||
<code>/friends</code> | |||
You may send an optional <code>If-None-Match</code> request header. See below for details. | |||
; Response | |||
<div class="treeview"> | |||
* {{Nbt|compound}}: Root tag. | |||
** {{Nbt|list|friends}}: Accepted friends. | |||
*** {{Nbt|compound}}: An accepted friend. | |||
**** {{Nbt|string|profileId}}: UUID of player. | |||
**** {{Nbt|string|name}}: Java Edition profile name of player. | |||
** {{Nbt|list|incomingRequests}}: Friend requests received from others. | |||
*** {{Nbt|compound}}: A friend request. | |||
**** {{Nbt|string|profileId}}: UUID of player. | |||
**** {{Nbt|string|name}}: Java Edition profile name of player. | |||
** {{Nbt|list|outgoingRequests}}: Friend requests sent to others. | |||
*** {{Nbt|compound}}: A friend request. | |||
**** {{Nbt|string|profileId}}: UUID of player. | |||
**** {{Nbt|string|name}}: Java Edition profile name of player. | |||
** {{Nbt|boolean|empty}}: If the friends list is empty. <code>true</code> if the player has 0 friends and 0 incoming or outgoing friend requests. | |||
</div> | |||
The response has an <code>ETag</code> response header, which you may save and reuse as the value for filling the <code>If-None-Match</code> request header in further requests. | |||
If the friends list has not changed since the last request, and the value of the <code>If-None-Match</code> request header matches the value of the <code>ETag</code> response header in the last request, Mojang will return a <code>HTTP 304 Not Modified</code> response. | |||
; Example | |||
<syntaxhighlight lang="json"> | |||
{ | |||
"friends": [ | |||
{ | |||
"profileId": "61699b2ed3274a019f1e0ea8c3f06bc6", | |||
"name": "Dinnerbone" | |||
} | |||
], | |||
"incomingRequests": [ | |||
{ | |||
"profileId": "853c80ef3c3749fdaa49938b674adae6", | |||
"name": "jeb_" | |||
} | |||
], | |||
"outgoingRequests": [ | |||
{ | |||
"profileId": "069a79f444e94726a5befca90e38aaf5", | |||
"name": "Notch" | |||
} | |||
], | |||
"empty": false | |||
} | |||
</syntaxhighlight> | |||
=== Friend management === | |||
; Request (PUT) | |||
<code>/friends</code> | |||
<div class="treeview"> | |||
; Payload | |||
* {{Nbt|compound|Root tag}} | |||
** {{Nbt|string|name}}: Profile name of the player to add or remove. Optional. | |||
** {{Nbt|string|profileId}}: UUID of the player to add or remove. Could be UUID with or without dashes Optional. | |||
** {{Nbt|string|updateType}}: Action to perform. Values: <code>ADD</code> for sending or accepting friend requests, <code>REMOVE</code> for removing friends or denying friend requests. | |||
</div> | |||
<code>name</code> and <code>profileId</code> are optional, but at least one must be provided. If both are present, <code>profileId</code> will be prioritized. | |||
; Example | |||
<syntaxhighlight lang="json"> | |||
{ | |||
"profileId": "069a79f444e94726a5befca90e38aaf5", | |||
"updateType": "ADD" | |||
} | |||
</syntaxhighlight> | |||
; Response | |||
Identical to [[#Get list of friends and friend requests]]. | |||
; Error | |||
<div class="treeview"> | |||
* {{Nbt|compound|Root tag}} | |||
** {{Nbt|string|path}}: The requested path. For this API Endpoint, it will always be <code>/friends</code>. | |||
** {{Nbt|compound|details}}: | |||
*** {{Nbt|string|status}}: Error code. See the table below. | |||
** {{Nbt|string|errorMessage}}: Human-readable error message. | |||
</div> | |||
{| class="wikitable mw-collapsible mw-collapsed" | |||
!HTTP status code | |||
!{{cd|details.status}} | |||
!{{cd|errorMessage}} | |||
!'''Explanation''' | |||
|- | |||
| rowspan="6" |400 | |||
|UNKNOWN_PROFILE | |||
|Name or profile does not exist | |||
| | |||
|- | |||
|CANNOT_ADD_SELF | |||
|Cannot add yourself as friend | |||
| | |||
|- | |||
|NOT_FRIENDS | |||
|Not friend with {{cd|profileId}} cannot remove | |||
| | |||
|- | |||
|ALREADY_FRIENDS | |||
|Already friend with {{cd|profileId}} | |||
| | |||
|- | |||
|INVALID_JSON | |||
|''Depends on request body'' | |||
|All required arguments presents in request body, but somehow cannot be decoded (e.g. wrong enum values, invalid UUID) | |||
|- | |||
|CONSTRAINT_VIOLATION | |||
|''Depends on request body'' | |||
|Some required arguments are missing | |||
|- | |||
|403 | |||
|INVITE_REJECTED | |||
|User does not have friends enabled or accept invites | |||
| | |||
|} | |||
=== Presence === | |||
This API is used for both querying friends' presences and reporting your current presence status. There is no standalone API for querying friends' presences. | |||
; Request (POST) | |||
<code>/presence</code> | |||
; Payload | |||
<div class="treeview"> | |||
* {{Nbt|compound|Root tag}} | |||
** {{Nbt|string|status}}: Your current presence status. See the table below for all available statuses. | |||
** {{Nbt|compound|joinInfo}}: Information relating to peer-to-peer multiplayer invites. Should be omitted in certain situations, see below. | |||
*** {{Nbt|string|value}}: Currently the game client always sends <code>null</code>. See below. | |||
*** {{Nbt|list|invites}}: List of invited players. Optional. | |||
**** {{Nbt|string}}: UUID of a invited player. Could be UUID with or without dashes. | |||
</div> | |||
For all presence statuses you may choose to omit the entire <code>joinInfo</code> object, even while reporting a <code>PLAYING_HOSTED_SERVER</code> status. Your friends' game client won't show "Ask to join" button in this situation. This is likely unintended behavior, and is subject to change in the near future. | |||
To report an <code>OFFLINE</code>, <code>ONLINE</code>, <code>PLAYING_OFFLINE</code> or <code>PLAYING_SERVER</code> status, you must have a string (1 <= length <= 256) or a numeric <code>joinInfo.value</code> present or omit the entire <code>joinInfo</code> object, otherwise Mojang will return a <code>HTTP 400 Bad Request</code> response. | |||
To report a <code>PLAYING_HOSTED_SERVER</code> status, you must have a null <code>joinInfo.value</code> present or omit the entire <code>joinInfo</code> object, otherwise Mojang will return a <code>HTTP 400 Bad Request</code> response. | |||
To report a <code>PLAYING_REALMS</code> status, you must have a numeric <code>joinInfo.value</code> present or omit the entire <code>joinInfo</code> object, otherwise Mojang will return a <code>HTTP 400 Bad Request</code> response. | |||
You can add non-friends (even dummy UUIDs, such as all zeroes) to <code>joinInfo.invites</code>, and Mojang will not return an error. However, since you're not friends, you won't appear in their friends list, so they won't receive your peer-to-peer multiplayer invite. | |||
; Response | |||
<div class="treeview"> | |||
* {{Nbt|compound|Root tag}} | |||
** {{Nbt|list|presence}}: List of presence statuses. Only online friends will be included within this list. | |||
*** {{Nbt|compound}}: A presence status. | |||
**** {{Nbt|string|profileId}}: UUID of the player, with dashes. | |||
**** {{Nbt|string|pmid}}: PMID used in peer-to-peer multiplayer, encoded in player's Access Token. UUID with dashes. | |||
**** {{Nbt|string|status}}: Player's presence status. See the table below for all available statuses. | |||
**** {{Nbt|compound|joinInfo}}: May be omitted in certain situations, see below. | |||
***** {{Nbt|string|value}}: Typically the same as the PMID, but can also be other values. | |||
***** {{Nbt|boolean|invited}}: Whether you are invited to join the peer-to-peer multiplayer session. | |||
**** {{Nbt|string|lastUpdated}}: Timestamp of the most recent presence report from this player. | |||
</div> | |||
<code>joinInfo</code> will be omitted if your friends omitted it when reporting their presence. This should not happen in vanilla Minecraft clients. | |||
For most cases <code>joinInfo.value</code> should be same as the PMID, but could also be something else if your friends send a non-null value while reporting their presences. This shouldn't happen in vanilla Minecraft clients. The meaning of this key is unclear, since the game client ignores it. | |||
<code>joinInfo.invited</code> could be <code>true</code> when <code>status</code> is <code>ONLINE</code>. This means your friend set "Presence: LIMITED" in their settings, which restricts the status to reporting either <code>ONLINE</code> or <code>OFFLINE</code>, while still allowing them to invite others to join their peer-to-peer multiplayer session. | |||
Currently, the game client doesn't seem to report the <code>OFFLINE</code> status before shutting down. After 10 minutes the server will consider the player offline due to their lack of presence updates. | |||
This API Endpoint will always return the latest presence status reported by your friends. It means, if you have two game instance running at the same time, a race condition may occur since both instances are trying to report their presence status, and which status your friends will see depends on when their client report their presence status. | |||
; All available presence statuses | |||
{| class="wikitable" | |||
|+ | |||
!{{cd|status}} | |||
!Text shows on UI | |||
!Explanation | |||
!Comment | |||
|- | |||
|OFFLINE | |||
|Offline | |||
|The player is offline | |||
|Offline players won't appear in their friends' presence responses | |||
|- | |||
|ONLINE | |||
|Online | |||
|The player is online but not playing any world or server (e.g. idling on the main menu, tweaking settings, etc.) | |||
|If the player set "Presence: LIMITED" in settings, the game will always report this | |||
|- | |||
|PLAYING_OFFLINE | |||
|Online (World) | |||
|The player is playing a world which isn't open to peer-to-peer multiplayer (Multiplayer set to "OFF" or "LAN") | |||
| | |||
|- | |||
|PLAYING_HOSTED_SERVER | |||
|Online (World) | |||
|The player is playing a world which is open to peer-to-peer multiplayer and allow others to ask to join | |||
|There will be an "Ask to join" button | |||
|- | |||
|PLAYING_REALMS | |||
|Online (Realms) | |||
|The player is playing a Realms server | |||
|Currently the game client will not report this | |||
|- | |||
|PLAYING_SERVER | |||
|Online (Server) | |||
|The player is playing on a third-party server | |||
|Currently the game client will not report this | |||
|} | |||
; Error | |||
Nearly identical to [[#Friends management]], with only <code>INVALID_JSON</code> and <code>CONSTRAINT_VIOLATION</code> present. If you have <code>joinInfo.value</code> present but violate the rules mentioned above, Mojang will return a <code>HTTP 400 Bad Request</code> error with only <code>path</code> and <code>errorMessage</code>. | |||
; Example request | |||
<syntaxhighlight lang="json"> | |||
{ | |||
"status": "PLAYING_HOSTED_SERVER", | |||
"joinInfo": { | |||
"value": null, | |||
"invites": [ | |||
"069a79f4-44e9-4726-a5be-fca90e38aaf5", | |||
"853c80ef-3c37-49fd-aa49-938b674adae6", | |||
"61699b2e-d327-4a01-9f1e-0ea8c3f06bc6" | |||
] | |||
} | |||
} | |||
</syntaxhighlight> | |||
; Example response | |||
<syntaxhighlight lang="json"> | |||
{ | |||
"presence": [ | |||
{ | |||
"profileId": "069a79f4-44e9-4726-a5be-fca90e38aaf5", | |||
"pmid": "c41da2ff-0874-4bbe-a28b-d122bc467b1f", | |||
"status": "ONLINE", | |||
"lastUpdated": "2026-05-15T19:25:58Z" | |||
}, | |||
{ | |||
"profileId": "853c80ef-3c37-49fd-aa49-938b674adae6", | |||
"pmid": "87c83220-965c-4d65-867f-8718f5ba04a6", | |||
"status": "ONLINE", | |||
"joinInfo": { | |||
"value": "87c83220-965c-4d65-867f-8718f5ba04a6", | |||
"invited": true | |||
}, | |||
"lastUpdated": "2026-05-15T19:26:12Z" | |||
}, | |||
{ | |||
"profileId": "61699b2e-d327-4a01-9f1e-0ea8c3f06bc6", | |||
"pmid": "f289feed-30ab-4039-bef8-5752ea9d2c1c", | |||
"status": "PLAYING_HOSTED_SERVER", | |||
"joinInfo": { | |||
"value": "f289feed-30ab-4039-bef8-5752ea9d2c1c", | |||
"invited": false | |||
}, | |||
"lastUpdated": "2026-05-15T19:26:32Z" | |||
} | |||
] | |||
} | |||
</syntaxhighlight> | |||
== Peer-to-peer multiplayer networking == | |||
{{outdated|section=y|edition=java|never=y|This feature was present only in development versions and the related API endpoints are no longer available.}} | |||
Peer-to-peer (P2P) multiplayer networking was introduced in [[Minecraft:Java Edition 26.2 Snapshot 7]] and removed in [[Minecraft:Java Edition 26.2 Pre-Release 1]]. It allows a host's singleplayer world to be joinable by friends over Peer-to-Peer [https://wikipedia.org/wiki/WebRTC WebRTC] data channels. A Mojang-hosted ''signaling service'' handles the handshake, gameplay traffic flows peer-to-peer (using [[Minecraft:w:Interactive_Connectivity_Establishment|ICE]]). The protocol described below is unstable and likely to change. | |||
General Properties: | |||
* The transport is DTLS-encrypted at the WebRTC layer; the client treats the resulting channel as a secure transport (<code>Connection.isSecureTransport() == true</code>). The Minecraft in-protocol AES encryption '''is skipped''', but the standard Mojang online-mode handshake (encryption request, key packet, Yggdrasil <code>hasJoinedServer</code> check) is still performed, without adding the Encryption/Decryption layers. | |||
* Both peers must be friends with each other (see [[#Friends list]]). | |||
* Only one handshake is permitted per client and host at a time. | |||
* The integrated server, when published peer-to-peer, '''forces online mode on''' regardless of any singleplayer-side configuration. The guest's identity is verified twice: once at signaling (via the PMID) and at the normal Minecraft login sequence. | |||
=== Identification === | |||
The signaling service routes messages by the '''PMID'''. A friend's PMID is obtained from the <code>pmid</code> field of their entry in their [[#Presence|presence]] response. | |||
When a peer-to-peer connection completes, the host resolves the guest's profile UUID locally from the PMID via its presence cache and '''pins it to the connection'''. The standard <code>ServerboundHelloPacket</code> profile UUID sent from the guest is ignored; the server obtains the guest's profile UUID via the normal login flow, and rejects the connection if the result does not match the UUID from its PMID presence cache. See [[#Full Connection sequence]] below. | |||
Authentication uses the same Mojang access token as the rest of the Mojang API, but is sent in the <code>x-mojangauth</code> HTTP header rather than as a <code>Bearer</code> token. | |||
The client also includes two stable identifiers for every request: | |||
* <code>Session-Id</code>: a UUID created once at startup. Sent on every subsequent request. | |||
* <code>Request-Id</code>: a fresh UUID generated per request. | |||
For each join attempt a third identifier is used: | |||
* <code>sessionId</code>: a fresh UUID generated by the guest. Every <code>FriendJoin</code> and <code>WebRtc</code> signaling message belonging to that join attempt includes this id. Reusing an old session id will be rejected by the host. | |||
=== Signaling service === | |||
==== Environment ==== | |||
The client selects one of two environments using the <code>signaling.environment</code> environment variable, falling back to a JVM system property of the same name, and finally defaulting to <code>PRODUCTION</code>. | |||
{| class="wikitable" | |||
!{{cd|signaling.environment}} | |||
!Base URL | |||
|- | |||
|<code>stage</code> / <code>staging</code> | |||
|<code><nowiki>https://signaling-afd.stage-6fd5f759.franchise.minecraft-services.net</nowiki></code> | |||
|- | |||
|<code>prod</code> / <code>production</code> (default) | |||
|<code><nowiki>https://signaling-afd.franchise.minecraft-services.net</nowiki></code> | |||
|} | |||
The <code>staging</code> URL does not work and produces an Azure Front Door Service 404 Not found page<ref>https://signaling-afd.stage-6fd5f759.franchise.minecraft-services.net</ref>. | |||
==== Discover the signaling WebSocket URL ==== | |||
; Request (GET) | |||
<code>/api/v1.0/configuration/java</code> | |||
Headers: | |||
* <code>x-mojangauth</code>: ''Minecraft access token''. Required. | |||
* <code>Session-Id</code>: Could be any non-empty value. Optional. See below. | |||
* <code>Request-Id</code>: Fresh per-request UUID with or without dashes, but could be also any non-empty value. Optional. See below. | |||
Both <code>Session-Id</code> and <code>Request-Id</code> can be any non-empty values or even be omitted. The server may behaves differently but it won't return errors. See below for details. | |||
; Response | |||
<div class="treeview"> | |||
* {{Nbt|compound|Root tag}} | |||
** {{Nbt|compound|result}}: | |||
*** {{Nbt|string|signalingUri}}: WebSocket endpoint, beginning with <code>wss://…</code>. | |||
*** {{Nbt|string|pingFrequency}}: A time in <code>hh:mm:ss</code> format, which tells the client how long to wait between sending ping messages. | |||
</div> | |||
The response will always contains a <code>Request-Id</code> header, which is a UUID without dashes matches the one you send in the request. However, if you omitted it in the request or filled it with other values cannot be recognized as UUID, it will be a new UUID without dashes generated by the server itself. | |||
The response may contains a <code>Session-Id</code> header matches the one you send in the request as-is, or will also omit it if you omitted it in the request. | |||
<code>result.signalingUri</code> may vary by multiple reasons, such as data encoded in access token or where you send the request. It doesn't matter if you and your friends connect to different signaling servers, although P2P multiplayer session invites and join requests are transferred on the signaling server, players connect to differnet signaling servers could still receive invites or join requests. | |||
For reference, <code>result.signalingUri</code> currently follows the format <code>wss://signal-{location}.franchise.minecraft-services.net</code>, where <code>{location}</code> corresponds to one of the following Microsoft Azure region identifiers: | |||
* <code>brazilsouth</code> | |||
* <code>centralindia</code> | |||
* <code>eastasia</code> | |||
* <code>eastus2</code> | |||
* <code>mexicocentral</code> | |||
* <code>northeurope</code> | |||
* <code>westeurope</code> | |||
* <code>westus2</code> | |||
These '''shouldn't be hardcoded''', as they are subject to change in the future. | |||
; Example | |||
<syntaxhighlight lang="json"> | |||
{ | |||
"result": { | |||
"signalingUri": "wss://signal-westeurope.franchise.minecraft-services.net", | |||
"pingFrequency": "00:01:00" | |||
} | |||
} | |||
</syntaxhighlight> | |||
The client caches the <code>signalingUri</code> for 5 minutes. | |||
==== Open the signaling WebSocket ==== | |||
; Request | |||
<code>GET {signalingUri}/ws/v1.0/messaging/connect/java</code> with a WebSocket upgrade, using the same three headers (<code>x-mojangauth</code>, <code>Session-Id</code>, <code>Request-Id</code>). | |||
Once the connection is open: | |||
* The client sends a <code>System_Ping_v1_0</code> JSON-RPC notification every 50 seconds. The server is expected to reply with a <code>System_Pong_v1_0</code> notification, which the client otherwise ignores. The <code>pingFrequency</code> from the discovery request is ignored by the client. | |||
* The server may push <code>Signaling_ReceiveMessage_v1_0</code> requests to the client at any time. | |||
If the WebSocket disconnects, or a connection attempt fails, and the client still needs signaling, it schedules another connection attempt after a random delay. The upper bound starts at 1 second and doubles after each failed attempt, capped at 30 seconds; the actual delay is uniformly chosen below that bound. If the client no longer needs signaling, it stays disconnected and the retry counter is reset. The counter is also reset after a successful signaling connection. | |||
The WebSocket server disconnects the client automatically after 120 seconds of inactivity, or after 60 seconds if no data is sent since the connection established. In both cases, the connection is terminated forcefully with close code <code>1006</code> (<code>Abnormal Closure</code>). This is applicable for all messages and isn't limited to <code>System_Ping_v1_0</code> messages. | |||
=== WebSocket Framing === | |||
The WebSocket protocol is JSON-RPC 2.0 over text frames. The framing rules used by the client are: | |||
* One JSON-RPC envelope per text message. Multi-frame messages are accumulated until the WebSocket flags the last frame. | |||
* The total size of a single message is capped at '''65 536 bytes'''. Oversize messages are dropped with a warning. | |||
* <code>id</code> must be an integer. The game client uses an '''increasing integer''' starting at <code>1</code>, the signaling server uses an '''increasing integer''' starting at <code>2</code>. The response must have the identical <code>id</code>. See below for details. | |||
* Unknown server-to-client methods are answered with the error <code>{"code":-32601,"message":"Method not found"}</code> (<code>METHOD_NOT_FOUND</code>). | |||
* Handler exceptions on incoming requests are answered with a JSON-RPC error <code>{"code":-32603,"message":"Internal error"}</code> (<code>INTERNAL_ERROR</code>). | |||
The signaling server actually accept any <code>id</code> including string, number or even null, but cannot be boolean. Sending a WebSocket frame with a boolean <code>id</code> will cause the signaling server to immediately terminate the connection with close code <code>1000</code> (<code>Normal Closure</code>). | |||
Although the signaling servers accept multiple data types for <code>id</code>, the game client strictly requires an integer <code>id</code>. If the signaling servers request the client with a non-integer <code>id</code>, this request will be dropped by the client. | |||
For <code>System_Ping_v1_0</code> requests sent by the client, the signaling server will just treat it as a notification, disregarding whether <code>id</code> was presented or not. The signaling server won't respond to it since it treats the request as a notification, instead, it will immediately send the client a <code>System_Pong_v1_0</code> request with <code>id</code> presented, while the game client doesn't really care about whether the server requested it, and also treats it as a notification which will never be responded. The only exception is the following one right below. | |||
In most cases, <code>id</code> can be freely reused. However, if the client send an <code>id</code> used in the previous <code>System_Ping_v1_0</code> requests, the signaling server will respond with an <code>InternalServerError</code> error, and the next time you reuse this <code>id</code> again, the server will behave normally. | |||
The server-to-client <code>Signaling_ReceiveMessage_v1_0</code> requests are delivered sequentially by the signaling service. After the server sends a <code>Signaling_ReceiveMessage_v1_0</code> request with an <code>id</code>, it waits for the client to send either a success response or an error response with the same <code>id</code> before sending the next server-to-client request on that WebSocket connection. If the client does not respond, later server-to-client requests are queued server-side and will not be delivered until the pending request is answered or the connection is closed. | |||
Standard JSON-RPC 2.0 envelopes: | |||
<syntaxhighlight lang="json"> | |||
// Request (id must be a JSON integer, parms can be omitted if no params are required) | |||
{ "jsonrpc": "2.0", "id": 1, "method": "<name>", "params": [...] } | |||
// Notification (id omitted, won't be responded unless error occurred) | |||
{ "jsonrpc": "2.0", "method": "<name>", "params": [...] } | |||
// Success response | |||
{ "jsonrpc": "2.0", "id": 1, "result": <any> } | |||
// Error response for backend error | |||
{ | |||
"jsonrpc": "2.0", | |||
"id": 1, | |||
"error": { | |||
"code": -32601, // Numeric error code defined in JSON-RPC 2.0 spec | |||
"message": "...", | |||
"data": { | |||
"Namespace": "ServiceRuntime", | |||
"Code": "<Name for the error code field>", | |||
"Message": "<Same content as in 'message'>", | |||
"CustomData": {}, | |||
"StackTrace": null, | |||
"InnerError": null | |||
} | |||
} | |||
} | |||
// Error response for JSON-RPC gateway error | |||
{ | |||
"jsonrpc": "2.0", | |||
"id": 1, | |||
"error": { | |||
"code": -32602, | |||
"message": "...", | |||
"data": { // may be omitted | |||
"type": "...", | |||
"message": "...", | |||
"stack": null, | |||
"code": -2146233088, | |||
"inner": { ... } // nested data object or null | |||
} | |||
} | |||
} | |||
</syntaxhighlight><code>"jsonrpc": "2.0"</code> is actually optional for both signaling server and game client, but it's recommended to present to respect the JSON-RPC 2.0 specification. | |||
If the client send a WebSocket frame that cannot be recognized as a JSON-RPC request (including sending a response containing a not requested <code>id</code>), the signaling server will close the connection with close code <code>1000</code> (<code>Normal Closure</code>). | |||
==== Methods ==== | |||
{| class="wikitable" | |||
!Direction | |||
!Method | |||
!Params | |||
!Result | |||
|- | |||
|C→S notification | |||
|<code>System_Ping_v1_0</code> | |||
|<code>[]</code> | |||
|— | |||
|- | |||
|S→C notification | |||
|<code>System_Pong_v1_0</code> | |||
|(ignored) | |||
|— | |||
|- | |||
|C→S request | |||
|<code>Signaling_TurnAuth_v1_0</code> | |||
|<code>[]</code> | |||
|<code>TurnAuthResult</code> (see [[#TURN credentials]]) | |||
|- | |||
|C→S request | |||
|<code>Signaling_SendClientMessage_v1_0</code> | |||
|<code>{ messageId, toPlayerId, message }</code> | |||
|<code>null</code> / <code>{}</code> (client ignores on success) | |||
|- | |||
|S→C request | |||
|<code>Signaling_ReceiveMessage_v1_0</code> | |||
|<code>[{ From, Message, Id }]</code> | |||
|<code>{}</code> (ACK only if <code>id</code> is present) | |||
|} | |||
The third parameter (<code>message</code>) of <code>Signaling_SendClientMessage_v1_0</code> is a '''stringified JSON''' value, not a nested JSON object. The signaling service treats it opaquely and forwards it verbatim as the <code>Message</code> field of <code>Signaling_ReceiveMessage_v1_0</code> on the recipient's side. | |||
==== Server-side errors ==== | |||
On C→S requests, the server may return a JSON-RPC error whose <code>data</code> is an object. The client checks an optional string field <code>data.Code</code>: | |||
{| class="wikitable" | |||
!Named Error code | |||
!Numeric Error code | |||
!Error Message | |||
!Explanation | |||
|- | |||
|<code>MissingOrExpiredIdentity</code> | |||
|<code>-32000</code> (Invalid request) | |||
|Player not registered with the service. | |||
|The target PMID is Invalid | |||
|- | |||
|<code>InternalServerError</code> | |||
|<code>-32000</code> (Invalid request) | |||
|''Depends on request'' | |||
|An internal server error occurred | |||
|- | |||
|(not presented) | |||
|<code>-32601</code> (Method not found) | |||
|No method by the name '<nowiki><method name></nowiki>' is found. | |||
| | |||
|- | |||
|(not presented) | |||
|<code>-32602</code> (Invalid params) | |||
|Unable to find method '<method name>/<presented params count>' on {no object} for the following reasons: An argument was not supplied for a required parameter. | |||
|Some required params are missing | |||
|- | |||
|(not presented) | |||
|<code>-32602</code> (Invalid params) | |||
|Unable to find method '<method name>/<presented params count>' on {no object} for the following reasons: ''depends on params presented'' | |||
|Some params presented cannot be recognized | |||
|} | |||
=== TURN credentials === | |||
Before creating a peer connection, the client fetches TURN authentication credentials from the WebSocket connection. | |||
; Request | |||
<syntaxhighlight lang="json"> | |||
{ | |||
"jsonrpc": "2.0", | |||
"id": 1, | |||
"method": "Signaling_TurnAuth_v1_0", | |||
"params": [] | |||
} | |||
</syntaxhighlight> | |||
; Response (<code>result</code>) | |||
<div class="treeview"> | |||
* {{Nbt|compound|Root tag}} | |||
** {{Nbt|int|ExpirationInSeconds}}: Time until the credentials should be refreshed, in seconds. | |||
** {{Nbt|list|TurnAuthServers}}: One or more TURN/STUN server entries. | |||
*** {{Nbt|compound}}: | |||
**** {{Nbt|string|Username}}: TURN username. | |||
**** {{Nbt|string|Password}}: TURN password (long-term credential). | |||
**** {{Nbt|list|Urls}}: List of STUN/TURN URLs. | |||
***** {{Nbt|string}}: TURN/STUN URL (e.g. <code>stun:...</code>, <code>turn:...</code>) | |||
</div> | </div> | ||
The result will be cached by the game client for 60 seconds in 26.2-snapshot-7 or <code>ExpirationInSeconds - 60</code> seconds in 26.2-snapshot-8. | |||
; Example | |||
<syntaxhighlight lang="json"> | |||
{ | |||
"jsonrpc": "2.0", | |||
"id": 1, | |||
"result": { | |||
"ExpirationInSeconds": 604799, | |||
"TurnAuthServers": [ | |||
{ | |||
"Username": "<long base64 string>", | |||
"Password": "<base64 string>", | |||
"Urls": [ | |||
"stun:relay.communication.microsoft.com:3478", | |||
"turn:relay.communication.microsoft.com:3478" | |||
] | |||
} | |||
] | |||
} | |||
} | |||
</syntaxhighlight> | |||
The client constructs a single <code>RTCIceServer</code> by taking the <code>Username</code> and <code>Password</code> of the '''first''' entry and the union of <code>Urls</code> from '''all''' entries. The peer-connection configuration disables TCP candidates and enables IPv6 (including on Wi-Fi). | |||
=== SignalingMessage format === | |||
All application-level signaling traffic between two peers is exchanged as <code>SignalingMessage</code> envelopes, encoded as a string in the third parameter of <code>Signaling_SendClientMessage_v1_0</code> (outbound) or in the <code>Message</code> field of <code>Signaling_ReceiveMessage_v1_0</code> (inbound). | |||
; Envelope | |||
<div class="treeview"> | |||
* {{Nbt|compound|Root tag}} | |||
** {{Nbt|string|type}}: Message kind. One of <code>JOIN_REQUEST</code>, <code>JOIN_ACCEPTED</code>, <code>JOIN_REJECTED</code>, <code>INVITE_DECLINED</code>, <code>OFFER</code>, <code>ANSWER</code>, <code>ICE_CANDIDATE</code>. | |||
** {{Nbt|string|sessionId}}: UUID identifying the join attempt this message belongs to. Always present. | |||
** {{Nbt|string|sdp}}: Full SDP body. Required for <code>OFFER</code> and <code>ANSWER</code>; omitted otherwise. May also carry the candidate string in the legacy <code>ICE_CANDIDATE</code> fallback (see below). | |||
** {{Nbt|compound|iceCandidate}}: ICE candidate. Required for <code>ICE_CANDIDATE</code>; omitted otherwise. | |||
*** {{Nbt|string|candidate}}: Candidate attribute line, e.g. <code>candidate:842163049 1 udp 2122260223 192.0.2.1 49152 typ host</code>. | |||
*** {{Nbt|string|sdpMid}}: SDP Media stream Id. Optional and defaults to <code>"0"</code>. | |||
*** {{Nbt|int|sdpMLineIndex}}: SDP m-line index, e.g. <code>0</code>. | |||
</div>If an <code>ICE_CANDIDATE</code> message arrives without a valid <code>iceCandidate</code> object, the decoder falls back to using the <code>sdp</code> field as the candidate attribute, with the defaults <code>sdpMid="0"</code> and <code>sdpMLineIndex=0</code>. | |||
==== Message types: ==== | |||
<syntaxhighlight lang="json"> | |||
// JOIN_REQUEST | |||
{ "type": "JOIN_REQUEST", "sessionId": "6b8b4567-327d-4ee8-90c2-0d8c1f9f1d9e" } | |||
// JOIN_ACCEPTED | |||
{ "type": "JOIN_ACCEPTED", "sessionId": "6b8b4567-327d-4ee8-90c2-0d8c1f9f1d9e" } | |||
// JOIN_REJECTED | |||
{ "type": "JOIN_REJECTED", "sessionId": "6b8b4567-327d-4ee8-90c2-0d8c1f9f1d9e" } | |||
// INVITE_DECLINED | |||
{ "type": "INVITE_DECLINED","sessionId": "a3f1c8d2-1111-2222-3333-444455556666" } | |||
// OFFER | |||
{ "type": "OFFER", "sessionId": "6b8b4567-...", "sdp": "v=0\r\no=- ...\r\n" } | |||
// ANSWER | |||
{ "type": "ANSWER", "sessionId": "6b8b4567-...", "sdp": "v=0\r\no=- ...\r\n" } | |||
// ICE_CANDIDATE | |||
{ | |||
"type": "ICE_CANDIDATE", | |||
"sessionId": "6b8b4567-...", | |||
"iceCandidate": { | |||
"candidate": "candidate:842163049 1 udp 2122260223 192.0.2.1 49152 typ host", | |||
"sdpMid": "0", | |||
"sdpMLineIndex": 0 | |||
} | |||
} | |||
</syntaxhighlight> | |||
==== Application semantics ==== | |||
{| class="wikitable" | |||
!{{cd|type}} | |||
!Direction | |||
!Meaning | |||
|- | |||
|<code>JOIN_REQUEST</code> | |||
|Guest → Host | |||
|Request to join the hosted world. Sent by the joiner with a fresh <code>sessionId</code>. | |||
|- | |||
|<code>JOIN_ACCEPTED</code> | |||
|Host → Guest | |||
|The host accepted the request. The guest must follow up by initiating the WebRTC handshake using the same <code>sessionId</code>. | |||
|- | |||
|<code>JOIN_REJECTED</code> | |||
|Host → Guest | |||
|The host rejected the request due to any reason. | |||
|- | |||
|<code>INVITE_DECLINED</code> | |||
|Guest → Host | |||
|The guest declined an invite that the host had previously listed in their [[#Presence|<code>joinInfo.invites</code>]]. The host clears the corresponding presence invite. | |||
|- | |||
|<code>OFFER</code> | |||
|Initiator (guest) → Host | |||
|WebRTC SDP offer for the handshake identified by <code>sessionId</code>. | |||
|- | |||
|<code>ANSWER</code> | |||
|Host → Initiator (guest) | |||
|WebRTC SDP answer for the same <code>sessionId</code>. | |||
|- | |||
|<code>ICE_CANDIDATE</code> | |||
|Either direction | |||
|ICE candidate produced by the local peer-connection. Always tagged with the same <code>sessionId</code>. | |||
|} | |||
Host-side validation: | |||
* If the host is not currently hosting a world, an incoming <code>JOIN_REQUEST</code> is rejected with <code>JOIN_REJECTED</code> immediately. | |||
* If the host and the sender are not mutual friends, the <code>JOIN_REQUEST</code> is '''silently dropped,''' no <code>JOIN_REJECTED</code> is sent. | |||
* If the sender's PMID is in the host's outstanding presence <code>joinInfo.invites</code>, the host '''auto-accepts''' (sends <code>JOIN_ACCEPTED</code>). | |||
* Otherwise, the request waits to be Accepted or Rejected by the user. | |||
The host filters incoming <code>OFFER</code> messages against the sender PMID and sessionId pair it previously sent <code>JOIN_ACCEPTED</code> for. Offers that do not match (no prior accept, wrong session id) are silently dropped. | |||
=== Sending and receiving signaling messages === | |||
==== Sending (<code>Signaling_SendClientMessage_v1_0</code>) ==== | |||
Every outbound application message is wrapped as follows. The third positional parameter is the <code>SignalingMessage</code> envelope '''serialised to a JSON string''', not a nested JSON object. | |||
<syntaxhighlight lang="json"> | |||
{ | |||
"jsonrpc": "2.0", | |||
"id": 2, | |||
"method": "Signaling_SendClientMessage_v1_0", | |||
"params": { | |||
"messageId": "5c2a87a0-2bd1-4b9f-9c50-1c2d5b4f8a91", // could be UUID with dashes or null, if it's null, signaling server server will generate a random UUID as messageId | |||
"toPlayerId": "069a79f4-44e9-4726-a5be-fca90e38aaf5", // PMID of recipient | |||
"message": "{\"type\":\"JOIN_REQUEST\",\"sessionId\":\"6b8b4567-327d-4ee8-90c2-0d8c1f9f1d9e\"}" // message to send | |||
} | |||
} | |||
</syntaxhighlight> | |||
On success the server responds with a JSON-RPC result that the client ignores. | |||
If the client requests the signaling server <code>Signaling_SendClientMessage_v1_0</code> but the PMID didn't connect to the signaling server at least once, the signaling server will return a <code>MissingOrExpiredIdentity</code> error. | |||
==== Receiving (<code>Signaling_ReceiveMessage_v1_0</code>) ==== | |||
Inbound application messages arrive as server-initiated JSON-RPC requests: | |||
<syntaxhighlight lang="json"> | |||
{ | |||
"jsonrpc": "2.0", | |||
"id": 17, | |||
"method": "Signaling_ReceiveMessage_v1_0", | |||
"params": { | |||
"From": "61699b2e-d327-4a01-9f1e-0ea8c3f06bc6", // PMID of sender | |||
"Message": "{\"type\":\"JOIN_REQUEST\",\"sessionId\":\"6b8b4567-327d-4ee8-90c2-0d8c1f9f1d9e\"}", // message received | |||
"Id": "5c2a87a0-2bd1-4b9f-9c50-1c2d5b4f8a91" // messageId | |||
} | |||
} | |||
</syntaxhighlight> | |||
If the request carries a numeric <code>id</code>, the client must immediately replies with <code>{"jsonrpc":"2.0","id":<id>,"result":{}}</code> as an ACK, otherwise the signaling service will not deliver later <code>Signaling_ReceiveMessage_v1_0</code> requests on the same WebSocket connection. | |||
If the client requests the signaling server <code>Signaling_SendClientMessage_v1_0</code> with a valid PMID but the message somehow cannot be delivered, the signaling server will send the client a <code>Signaling_ReceiveMessage_v1_0</code> request immediately with opposite's PlayFab ID (a 16 characters length hex string) presented in <code>params[0].From</code>, along with a service envelope describe below presented in <code>params[0].Message</code>. The PlayFab ID <code>params[0].From</code> looks like a bug and may subject to changed in future. | |||
==== Service envelope (delivery failure) ==== | |||
The inner <code>Message</code> may also be a '''service envelope''' indicating a delivery failure rather than a peer message. A service envelope is a JSON object with a top-level numeric <code>Code</code> and a <code>Message</code> field. It does not include a <code>type</code> field: | |||
<syntaxhighlight lang="json"> | |||
{ "Code": 1, "Message": "Player 069a79f4-... is not reachable" } | |||
</syntaxhighlight> | |||
{| class="wikitable" | |||
!{{cd|Code}} | |||
!Symbolic name | |||
!Meaning | |||
|- | |||
|1 | |||
|<code>PLAYER_UNREACHABLE</code> | |||
|The target PMID is not currently connected to signaling. | |||
|- | |||
|2 | |||
|<code>MESSAGE_DELIVERY_FAILED</code> | |||
|The signaling service could not deliver the message. | |||
|- | |||
|3 | |||
|<code>TURN_AUTH_FAILED</code> | |||
|Server-side TURN auth failure for the underlying flow. | |||
|} | |||
=== Full Connection sequence === | |||
The full sequence for guest '''A''' joining host '''B''' | |||
# A sends <code>Signaling_TurnAuth_v1_0</code> to obtain TURN credentials and constructs an <code>RTCIceServer</code>. | |||
# A generates a fresh <code>sessionId</code> and sends <code>JOIN_REQUEST</code> targeted at B's PMID, wrapped in <code>Signaling_SendClientMessage_v1_0</code>. The join request expires after one minute on As side. | |||
# B's client receives <code>JOIN_REQUEST</code> via <code>Signaling_ReceiveMessage_v1_0</code>. After friend and hosting checks, it either: | |||
#* Auto-accepts (if A is in B's outstanding presence <code>invites</code>); or | |||
#* Silently drops (if A is not a friend); or | |||
#* Sends <code>JOIN_REJECTED</code> immediately (if B is not hosting); or | |||
#* Waits for player approval before accepting or rejecting the join request. | |||
# On acceptance, B records (A's <code>PMID</code> and <code>sessionId</code>), and sends <code>JOIN_ACCEPTED</code> with the same <code>sessionId</code>. | |||
# A receives <code>JOIN_ACCEPTED</code>, builds the peer connection, creates a data channel labelled <code>"minecraft"</code>, and sends the SDP as <code>OFFER</code>. A also starts a 10-second handshake timer. | |||
# As local ICE candidates appear on A, each is sent as <code>ICE_CANDIDATE</code> with the same <code>sessionId</code>. | |||
# B receives <code>OFFER</code>, validates that <code>(from, sessionId)</code> matches, builds its own peer connection, generates the SDP answer, and sends it as <code>ANSWER</code>. B starts its own 10-second timer and sends its ICE candidates back as <code>ICE_CANDIDATE</code>. | |||
# Both sides add incoming <code>ICE_CANDIDATE</code> messages into <code>addIceCandidate</code>. When the data channel reaches the <code>OPEN</code> state, the handshake is considered complete and both 10-second timers are cancelled. | |||
# A wraps the open data channel as a Netty channel and runs the standard Minecraft login flow over it: sends <code>ServerboundHelloPacket</code> announcing its profile name and profile UUID. The host '''does''' perform the standard online-mode handshake (<code>ClientboundHelloPacket</code> → <code>ServerboundKeyPacket</code> → Mojang <code>hasJoinedServer</code> check); the only difference from a normal TCP login is that the negotiated symmetric key is '''not''' applied to the channel (the connection is already DTLS-encrypted, so <code>setEncryptionKey</code> is skipped). The auth-derived profile UUID is then checked against the connection's pinned <code>intendedProfileId</code>; if they do not match, the host disconnects with a Connection failed message. | |||
# B wraps the open data channel similarly and hands it to its integrated server as a pre-authenticated connection bound to A's profile UUID (resolved from A's PMID). | |||
# A and B both check whether they still need signaling; if not, the WebSocket is closed. | |||
==== <code>RTCConfiguration</code> used by the client ==== | |||
For both sides, the configuration is identical: | |||
<syntaxhighlight lang="java"> | |||
RTCConfiguration cfg = new RTCConfiguration(); | |||
cfg.iceServers.add(turnAuth); | |||
cfg.portAllocatorConfig | |||
.setDisableTcp(true) | |||
.setEnableIpv6(true) | |||
.setEnableIpv6OnWifi(true); | |||
</syntaxhighlight> | |||
==== Data channel parameters (initiator only) ==== | |||
<syntaxhighlight lang="java"> | |||
RTCDataChannelInit init = new RTCDataChannelInit(); | |||
init.ordered = true; | |||
init.maxRetransmits = -1; | |||
init.priority = RTCPriorityType.HIGH; | |||
peerConnection.createDataChannel("minecraft", init); | |||
</syntaxhighlight> | |||
The responder does '''not''' create its own data channel; it receives the initiator's channel via <code>onDataChannel</code>. | |||
==== Hand-off to standard Minecraft Protocol ==== | |||
Once the data channel reaches <code>OPEN</code>: | |||
* '''Guest side''': constructs a Netty channel adapter over the data channel, builds a Minecraft serverbound play connection using <code>"rtc-peer"</code> as the placeholder host name and port <code>0</code>, sends a single <code>ServerboundHelloPacket(profileName, profileId)</code>. From that point on the standard login, configuration and play protocol runs over the data channel. | |||
* '''Host side''': constructs the same Netty channel adapter and passes it, together with the guest's profile UUID (resolved from the PMID), into the integrated server's authenticated channels. The server thereafter treats the connection as a normally-authenticated, connected player. | |||
==== Outbound chunking and backpressure ==== | |||
The client and server enforce the following on outbound data-channel writes: | |||
* Every Netty write that exceeds 256 KiB is split into 256 KiB chunks before being sent. | |||
* When the WebRTC <code>bufferedAmount</code> on the data channel exceeds 1 MiB, the Netty channel is marked '''non-writable''' and the application is expected to back off. | |||
* When <code>bufferedAmount</code> falls back below 256 KiB, the Netty channel is marked '''writable''' again and writes resume. | |||
== History == | == History == | ||
| Line 736: | Line 1,560: | ||
|{{HistoryLine||September 13, 2022|link=https://help.minecraft.net/hc/en-us/articles/8969841895693-Username-History-API-Removal-FAQ-|The endpoint for querying names a player used to use is removed. The endpoint was <code><nowiki>https://api.mojang.com/user/profiles/</nowiki><''UUID''>/names</code>.}} | |{{HistoryLine||September 13, 2022|link=https://help.minecraft.net/hc/en-us/articles/8969841895693-Username-History-API-Removal-FAQ-|The endpoint for querying names a player used to use is removed. The endpoint was <code><nowiki>https://api.mojang.com/user/profiles/</nowiki><''UUID''>/names</code>.}} | ||
|{{HistoryLine||January 15, 2025|link=https://bugs.mojang.com/browse/WEB-7591?focusedId=1375404&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-1375404|The endpoint for querying UUIDs based on a player's name has experimental rate limits introduced. The endpoint is <code><nowiki>https://api.mojang.com/users/profiles/minecraft/</nowiki><''username''>/names</code>.}} | |{{HistoryLine||January 15, 2025|link=https://bugs.mojang.com/browse/WEB-7591?focusedId=1375404&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-1375404|The endpoint for querying UUIDs based on a player's name has experimental rate limits introduced. The endpoint is <code><nowiki>https://api.mojang.com/users/profiles/minecraft/</nowiki><''username''>/names</code>.}} | ||
}} | |{{HistoryLine||May 12, 2026|link=https://www.minecraft.net/es-es/article/friends-list-for-java-edition|Mojang introduced friend lists for Java Edition alongside the WebSocket/WebRTC P2P protocol.}}}} | ||
== References == | == References == | ||
| Line 770: | Line 1,594: | ||
{{license wiki.vg}} | {{license wiki.vg}} | ||
de:Mojang API | [[Minecraft:de:Mojang API]] | ||
lzh:魔贊卯口 | [[Minecraft:lzh:魔贊卯口]] | ||
zh:Mojang API | [[Minecraft:zh:Mojang API]] | ||
Latest revision as of 11:11, 1 June 2026
This page provides documentation for API endpoints provided by Minecraft:Mojang Studios which allows user to query player data and make changes programmatically.
Most of the API has a per-IP ratelimit of 200 requests per 2 minutes. For IPv6, the ratelimits are bucketed by /56 subnet. Some endpoints have different ratelimits.
Request and response
For requests with a payload, the following restrictions apply:
- Request must include the
Content-Typeheader, which must be application/json. Otherwise, the server returns HTTP 415. - Payload must be a valid json. Otherwise, the server returns HTTP 400.
If the request is successful, the server:
- Returns a successful response code (HTTP 2XX)
- Returns an empty payload (with HTTP 204) or a valid json
Otherwise, if the request fails, the server returns a non-2XX HTTP status code with this payload:
- Template:Nbt: Root tag.
- Template:Nbt: Error identifier
- Template:Nbt: Description of the error
- Template:Nbt: Description of the cause of the error
These are some common causes:
| HTTP status code | Template:Cd | Template:Cd | Explanation |
|---|---|---|---|
| 400 | IllegalArgumentException | Depends on the endpoint | Incorrect or invalid argument in the request. |
| MismatchedInputException | JSON payload does not meet the required schema or payload is not an invalid JSON. | ||
| JsonParseException | |||
| 401 | (empty payload) | (empty payload) | Endpoint requires authentication, but request header does not have an Authorization header or the token is invalid.
|
| Unauthorized | The request requires user authentication | Endpoint requires authentication, but request header does not have an Authorization header.
| |
| 403 | ForbiddenOperationException | Forbidden | Invalid auth token. |
| Template:Tc | Your account has been suspended. Please contact customer service. | Account has been banned/suspended state by triggering a high volume of erroneous requests. See #Account suspensions. | |
| 404 | Not Found | The server has not found anything matching the request URI | Endpoint does not exist. |
| 405 | Method Not Allowed | The method specified in the request is not allowed for the resource identified by the request URI | Request method is not supported. |
| 415 | Unsupported Media Type | The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method | Template:Cd request header does not match the type the endpoint allows. |
Account suspensions
A Minecraft account can be entered into a banned/suspended state by triggering a high volume of erroneous requests, such as a high volume of 429s while uploading a skin.
These suspensions appear to be temporary, although this is speculation and the exact functionality of this automatic suspension system is unknown.
POST https://api.minecraftservices.com/authentication/login_with_xbox
Returns 403 with the following JSON data <syntaxhighlight lang="json"> { "path": "/authentication/login_with_xbox", "details": { "reason": "ACCOUNT_SUSPENDED" }, "errorMessage": "Your account has been suspended. Please contact customer service." } </syntaxhighlight>
Other services will report that the account is banned, such as servers serving the otherwise unused message You are banned from playing online.
These suspensions typically clear within 24 hours but may require contacting Minecraft support if they don't.
Query player information
These endpoints do not need an access token, and some endpoints can query registered account that do not own the game.
Query player's UUID
- Input
Player's name (case insensitive).
- Request (GET)
https://api.mojang.com/users/profiles/minecraft/<player name>- This will occasionally return random 403 errors due to a misconfiguration<ref>https://bugs.mojang.com/browse/WEB-7591?focusedId=1375404&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-1375404</ref> by Mojang.
https://api.minecraftservices.com/minecraft/profile/lookup/name/<player name>https://api.mojang.com/minecraft/profile/lookup/name/<player name>
- Response
- Template:Nbt Root tag
- Template:Nbt: Minecraft:UUID of the player.
- Template:Nbt: Name of the player, case sensitive.
- Template:Nbt: Included in response if the account has not migrated to Mojang account.
- Template:Nbt: Included in response if the account does not own the game.
- Example
https://api.mojang.com/users/profiles/minecraft/jeb_
Player jeb_'s UUID.
<syntaxhighlight lang="json"> {
"name": "jeb_", "id": "853c80ef3c3749fdaa49938b674adae6"
} </syntaxhighlight>
- Error
- HTTP 404 is returned if no player with such name exists.
Query player's username
- Input
Player's UUID.
- Request (GET)
https://api.minecraftservices.com/minecraft/profile/lookup/<UUID>
- Response
The same as #Query player's UUID.
Query player UUIDs in batch
- Payload
A JSON array with no more than 10 player names (case insensitive).
- Request (POST)
https://api.mojang.com/profiles/minecrafthttps://api.minecraftservices.com/minecraft/profile/lookup/bulk/bynamehttps://api.mojang.com/minecraft/profile/lookup/bulk/byname
- Response
- Template:Nbt The UUID list for all the players. For players that does not exist, no result is returned.
- Template:Nbt Player name.
- Template:Nbt: Minecraft:UUID of the player.
- Template:Nbt: Name of the player, case sensitive.
- Template:Nbt: Included in response if the account has not migrated to Mojang account.
- Template:Nbt: Included in response if the account does not own the game.
- Template:Nbt Player name.
- Example
Request with payload ["jeb_","notch"].
<syntaxhighlight lang="json">[
{
"id": "853c80ef3c3749fdaa49938b674adae6",
"name": "jeb_"
},
{
"id": "069a79f444e94726a5befca90e38aaf5",
"name": "Notch"
}
]</syntaxhighlight>
| HTTP status code | Template:Cd | Template:Cd | Explanation |
|---|---|---|---|
| 400 | CONSTRAINT_VIOLATION | size must be between 1 and 10 | RequestPayload is an empty array. |
| RequestPayload is has more than 10 elements. | |||
| Invalid profile name | RequestPayload includes an empty string. |
Query player's skin and cape
- Input
Player UUID and whether the request is signed.
- Request (GET)
https://sessionserver.mojang.com/session/minecraft/profile/<UUID>- Ratelimited at about 400 requests per 10 seconds.
https://sessionserver.mojang.com/session/minecraft/profile/<UUID>?unsigned=false
- Response
- Template:Nbt Root tag
- Template:Nbt: Player's Minecraft:UUID.
- Template:Nbt: Player name, case sensitive.
- Template:Nbt: Included in response if the account has not migrated to Mojang account.
- Template:Nbt: A list of player properties.
- Template:Nbt: Name of the property. For now, the only property that exists is
textures. - Template:Nbt: Signature signed with Yggdrasil private key as Base64 string, only exists when
unsigned=false. - Template:Nbt: Base64 string with all player textures (skin and cape). The decoded string includes:
- Template:Nbt Texture object.
- Template:Nbt: Unix time in milliseconds the texture is accessed.
- Template:Nbt: Player's UUID without dashes.
- Template:Nbt: Player name.
- Template:Nbt: Only exists when
unsigned=false. - Template:Nbt: Texture.
- Template:Nbt: Minecraft:Skin texture. This does not exist if the player does not have a custom skin.
- Template:Nbt: URL to the skin texture.
- Template:Nbt: Optional. Metadata for the skin.
- Template:Nbt:
slim. Only exists when skin model is Alex. When skin model is Steve, this metadata does not exist.
- Template:Nbt:
- Template:Nbt: Minecraft:Cape texture. If the player does not have a cape, this does not exist.
- Template:Nbt: URL to the cape texture.
- Template:Nbt: Minecraft:Skin texture. This does not exist if the player does not have a custom skin.
- Template:Nbt Texture object.
- Template:Nbt: Name of the property. For now, the only property that exists is
- Example
https://sessionserver.mojang.com/session/minecraft/profile/853c80ef3c3749fdaa49938b674adae6
Returns:
<syntaxhighlight lang="json">
{
"id": "853c80ef3c3749fdaa49938b674adae6",
"name": "jeb_",
"properties": [
{
"name": "textures",
"value": "ewogICJ0aW1lc3R..."
}
]
} </syntaxhighlight> Content in Template:Nbt after Base64 decoded: <syntaxhighlight lang="json"> {
"timestamp": 1653838459263,
"profileId": "853c80ef3c3749fdaa49938b674adae6",
"profileName": "jeb_",
"textures": {
"SKIN": {
"url": "http://textures.minecraft.net/texture/7fd9ba42a7c81eeea22f1524271ae85a8e045ce0af5a6ae16c6406ae917e68b5"
},
"CAPE": {
"url": "http://textures.minecraft.net/texture/9e507afc56359978a3eb3e32367042b853cddd0995d17d0da995662913fb00f7"
}
}
} </syntaxhighlight>
- Error
| HTTP status code | Template:Cd | Template:Cd | Explanation |
|---|---|---|---|
| 204 | (empty payload) | (empty payload) | This UUID does not have an associated player |
| 400 | (empty) | Not a valid UUID: <inputted argument> | UUID is invalid. |
Microsoft authentication
Authentication for Microsoft accounts. Before using Microsoft auth, an Microsoft Azure Application must be created to obtain OAuth 2.0 client ID and token, which can then be used to obtain a Microsoft token. When obtaining the token, the Template:Cd parameter should include Template:Samp to obtain an Xbox Live token.
Obtain an Xbox Live token with Microsoft token
- Payload
- Template:Nbt Root tag
- Template:Nbt: Authentication properties
- Template:Nbt: Login method. This should be
RPS. - Template:Nbt: Website name. This should be
user.auth.xboxlive.com. - Template:Nbt: Ticket used for logging in. Value should be
d=<Microsoft access token>.
- Template:Nbt: Login method. This should be
- Template:Nbt: Replying party. This should be
http://auth.xboxlive.com. - Template:Nbt: Type of the access token. This should be
JWT.
- Template:Nbt: Authentication properties
- Request (POST)
https://user.auth.xboxlive.com/user/authenticate
SSL renegotiation required in SSL implementation.
- Response
- Template:Nbt Root tag
- Template:Nbt: Time when obtaining the Xbox Live token.
- Template:Nbt: Time the Xbox Live token is expired.
- Template:Nbt: Xbox Live access token.
- Template:Nbt: Unknown.
- Template:Nbt: Unknown.
- Template:Nbt
- Template:Nbt: User hashcode.
- Template:Nbt
- Template:Nbt: Unknown.
Obtain an XSTS token with Xbox Live token
- Payload
- Template:Nbt Root tag
- Template:Nbt: Auth properties.
- Template:Nbt: Sandbox ID. This should be
RETAIL. - Template:Nbt: User's Xbox Live token.
- Template:Nbt: User's Xbox Live token obtained in the previous step.
- Template:Nbt: Sandbox ID. This should be
- Template:Nbt: Replying party. This should be
rp://api.minecraftservices.com/. - Template:Nbt: Type of the access token. This should be
JWT.
- Template:Nbt: Auth properties.
- Request (POST)
https://xsts.auth.xboxlive.com/xsts/authorize
SSL renegotiation required in SSL implementation.
- Response
- Template:Nbt Root tag
- Template:Nbt: Time when obtaining the XSTS token.
- Template:Nbt: Time the XSTS token is expired.
- Template:Nbt: XSTS access token.
- Template:Nbt: Unknown.
- Template:Nbt: Unknown.
- Template:Nbt
- Template:Nbt: User hashcode.
- Template:Nbt
- Template:Nbt: Unknown.
- Error
HTTP 401 is returned if an error occurs in obtaining the XSTS token.
Obtain Minecraft access token with XSTS token
- Payload
- Template:Nbt Root tag
- Template:Nbt: Identity token. The value should be
XBL3.0 x=<User hashcode>;<XSTS access token>.
- Template:Nbt: Identity token. The value should be
- Request (POST)
https://api.minecraftservices.com/authentication/login_with_xbox
- Response
- Template:Nbt Root tag
- Template:Nbt: UUID (not the UUID for the player)
- Template:Nbt: Unknown, empty.
- Template:Nbt: Minecraft access token.
- Template:Nbt: Token type. This is always
Bearer. - Template:Nbt: Time period until the token expires in seconds.
Check if the account owns Minecraft
- Request header
Authorization should be Bearer <Minecraft access token>.
- Request (GET)
https://api.minecraftservices.com/entitlements/license?requestId=<v4 UUID>
- Response
If the account owns Minecraft, returns:
- Template:Nbt Root tag
- Template:Nbt: Products licensed to use.
- Template:Nbt: Data name, either
product_minecraft,game_minecraft,product_minecraft_bedrockorgame_minecraft_bedrock.
- Template:Nbt: Data name, either
- Template:Nbt: JWT signature.
- Template:Nbt: Unknown.
- Template:Nbt: Products licensed to use.
If the account does not own Minecraft or is playing with Xbox Game Pass, empty payload is returned.
Player config
These endpoints are at https://api.minecraftservices.com, and all requires the Authorization header in request with value Bearer <Minecraft access token>. HTTP 401 is returned if token is missing or invalid.
Query player profile
- Request (GET)
/minecraft/profile
- Response
- Template:Nbt Root tag
- Template:Nbt: Player's UUID.
- Template:Nbt: Player name.
- Template:Nbt: A list of info of all the skins the player owns.
- Template:Nbt: A skin.
- Template:Nbt: Skin's UUID.
- Template:Nbt: Usage status for the skin.
ACTIVEwhen the skin is the player's current skin, andINACTIVEwhen the skin was previously used. - Template:Nbt: URL to the skin.
- Template:Nbt: Skin variant.
CLASSICfor the Steve model andSLIMfor the Alex model.
- Template:Nbt: A skin.
- Template:Nbt: A list of info of all the capes the player owns.
- Template:Nbt: A cape.
- Template:Nbt: Cape's UUID.
- Template:Nbt: Usage status for the cape.
ACTIVEwhen the cape is the player's current cape, andINACTIVEwhen the cape is owned but not displayed in-game. - Template:Nbt: URL to the cape.
- Template:Nbt: Alias for the cape.
- Template:Nbt: A cape.
Query player attributes
- Request (GET)
/player/attributes
- Response
- Template:Nbt Root tag
- Template:Nbt: Player's privileges
- Template:Nbt: Privilege of accepting chat messages.
- Template:Nbt: Privilege of joining servers.
- Template:Nbt: Privilege of joining Realms.
- Template:Nbt: Privilege of sending telemetry.
- Template:Nbt: Privilege of sending optional telemetry.
- Template:Nbt: Profanity filter settings.
- Template:Nbt: If Realms profanity filter is on.
- Template:Nbt:
- Template:Nbt: Whether the player has enabled the friend list feature. Values:
ENABLED,DISABLED. - Template:Nbt: Whether the player allows friend requests. Values:
ENABLED,DISABLED.
- Template:Nbt: Whether the player has enabled the friend list feature. Values:
- Template:Nbt:
- Template:Nbt: Serves the same purpose as Template:Nbt, with the addition of the
FRIENDS_ONLYvalue for hiding chat messages from non-friends. Template:Nbt remains as a form of backwards compatibility to versions which do not query Template:Nbt. Values:ENABLED,DISABLED,FRIENDS_ONLY.
- Template:Nbt: Serves the same purpose as Template:Nbt, with the addition of the
- Template:Nbt: Player's ban status
- Template:Nbt: Scope in which the player is banned.
- Template:Nbt: Ban scope. If the player is not banned, these objects do not exist. Only
MULTIPLAYERexists.- Template:Nbt: UUID of the ban.
- Template:Nbt: When the ban expires. This does not exist if the player is permanently banned.
- Template:Nbt: Reason for the ban.
- Template:Nbt: Ban message displayed.
- Template:Nbt: Ban scope. If the player is not banned, these objects do not exist. Only
- Template:Nbt: Scope in which the player is banned.
- Template:Nbt: Player's privileges
Modify player attributes
- Payload
A JSON array containing the desired keys to update, right now, only orofanityFilterPreferences and friendsPreferences can be updated
- Example
- Template:Nbt Root tag
- Template:Nbt: Realms profanity filter options.
- Template:Nbt: If filter is on.
- Template:Nbt: Friends list options.
- Template:Nbt: Whether the player has enabled the friend list feature. Values:
ENABLED,DISABLED - Template:Nbt: Whether the player allows friend requests. Values:
ENABLED,DISABLED.
- Template:Nbt: Whether the player has enabled the friend list feature. Values:
- Template:Nbt: Realms profanity filter options.
- Request (POST)
/player/attributes
- Response
The same as #Query player attributes.
Get list of blocked users
- Request (GET)
/privacy/blocklist
- Response
- Template:Nbt Root tag
- Template:Nbt: A list of blocked players whose chat messages and Realms invitations are ignored.
- Template:Nbt: UUID for the blocked player.
- Template:Nbt: A list of blocked players whose chat messages and Realms invitations are ignored.
Get keypair for signature
- Request (POST)
/player/certificates
- Response
- Template:Nbt Root tag
- Template:Nbt: Keypair used for signature.
- Template:Nbt: Private key. Starts with Template:Cd and ends with Template:Cd.
- Template:Nbt: Public key. Starts withTemplate:Cd and ends with Template:Cd.
- Template:Nbt: Deprecated, see below.
- Template:Nbt: Public key signature.
- Template:Nbt: When the keypair expires.
- Template:Nbt: When the keypair should be refreshed.
- Template:Nbt: Keypair used for signature.
Query player's name change information
- Request (GET)
/minecraft/profile/namechange
- Response
- Template:Nbt Root tag
- Template:Nbt: The last time the name has been changed.
- Template:Nbt: The time the player profile is created.
- Template:Nbt: If the player can change name.
Check gift code validity
- Request (GET)
/productvoucher/giftcode
- Response
Returns HTTP 200 or 204 if the gift code is valid. Otherwise returns HTTP 404.
Check name availability
- Request (GET)
/minecraft/profile/name/<name to be checked>/available
- This endpoint has a per-account ratelimit of 20 requests per 5 minutes.
- Response
- Template:Nbt Root tag
- Template:Nbt: Status of the name.
DUPLICATEmeans the name is taken.AVAILABLEmeans the name is available.NOT_ALLOWEDmeans the name does not meet requirements.
- Template:Nbt: Status of the name.
Change name
- Input
Name to change to
- Request (PUT)
/minecraft/profile/name/<name to change to>
- Response
- Template:Nbt Root tag
- Template:Nbt: Player's UUID.
- Template:Nbt: Player name.
- Template:Nbt: A list of info of all the skins the player owns.
- Template:Nbt: A skin.
- Template:Nbt: Cape's UUID.
- Template:Nbt: Usage status for the cape.
- Template:Nbt: URL to the skin.
- Template:Nbt: Skin variant.
CLASSICfor the Steve model andSLIMfor the Alex model.
- Template:Nbt: A skin.
- Template:Nbt: A list of info of all the capes the player owns.
- Template:Nbt: A cape.
- Template:Nbt: Cape's UUID.
- Template:Nbt: Usage status for the cape.
- Template:Nbt: URL to the cape.
- Template:Nbt: Alias for the cape.
- Template:Nbt: A cape.
- Error
| HTTP status code | Template:Cd | Template:Cd | Explanation |
|---|---|---|---|
| 400 | CONSTRAINT_VIOLATION | changeProfileName.profileName: Invalid profile name | Name does not meet requirement. The name must have less than or equal to 16 characters and must consist of alphanumericals and underscores. |
| 403 | (empty) | Could not change name for profile | Cannot change name. If detail.status is DUPLICATE, the name has already been taken.
|
| 429 | - | - | Too many rename requests sent. |
Change skin
- Payload
- Template:Nbt Root tag
- Template:Nbt: Skin variant.
classicfor the Steve model andslimfor the Alex model. - Template:Nbt: URL to the skin.
- Template:Nbt: Skin variant.
- Request (POST)
/minecraft/profile/skins
- Response
Returns the profile if operation succeeds.
Upload skin
- Payload
Payload is made up of two parts:
- Template:Cd: Skin variant.
classicfor the Steve model andslimfor the Alex model. - Template:Cd: Image data for the new skin. See example below.
- Request (POST)
/minecraft/profile/skins
- Example
<syntaxhighlight lang="bash"> curl -X POST -H "Authorization: Bearer <access token>" -F variant=classic -F file="@steeevee.png;type=image/png" https://api.minecraftservices.com/minecraft/profile/skins </syntaxhighlight>
- Response
Returns the profile if operation succeeds.
Reset skin
- Input
Player's UUID.
- Request (DELETE)
/minecraft/profile/skins/active
- Response
Returns the profile if operation succeeds.
Hide cape
- Request (DELETE)
/minecraft/profile/capes/active
- Response
Returns the profile if operation succeeds.
Show cape
- Payload
- Template:Nbt Root tag
- Template:Nbt: UUID for the cape to activate
- Request (PUT)
/minecraft/profile/capes/active
- Response
Returns the profile if operation succeeds.
- Error
| HTTP status code | Template:Cd | Template:Cd | Explanation |
|---|---|---|---|
| 400 | (empty) | profile does not own cape | The player does not own the cape. |
Server
Query blocked server list
- Request (GET)
https://sessionserver.mojang.com/blockedservers
- Response
A text file where every line is the SHA-1 hash of a blocked server.
Verify login session on client
- Payload
- Template:Nbt Root tag
- Template:Nbt: Minecraft access token.
- Template:Nbt: Player UUID without dashes.
- Template:Nbt: Server ID. See below.
Server ID is obtained from the following algorithm:
<syntaxhighlight lang="java"> public static String generateServerId(String baseServerId, // Base server ID, usually an empty string""
PublicKey publicKey, // Server's RSA public key
SecretKey secretKey // The symmetric AES secret key used between server and client
) throws Exception {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
messageDigest.update(baseServerId.getBytes("ISO_8859_1"));
messageDigest.update(secretKey.getEncoded());
messageDigest.update(publicKey.getEncoded());
byte[] digestData = messageDigest.digest();
return new BigInteger(digestData).toString(16);
} </syntaxhighlight>
- Request (POST)
https://sessionserver.mojang.com/session/minecraft/join
- This endpoint has a per-account ratelimit of 6 joins per 30 seconds.
- Response
Returns HTTP 204 if authentication passes.
Verify login session on server
- Input
Case insensitive player name, server ID obtained by the above algorithm and the client IP (optional).
- Request (GET)
https://sessionserver.mojang.com/session/minecraft/hasJoined?username=<player name>&serverId=<Server ID>&ip=<Client IP>
- Response
Returns this payload if verification passes.
Get Mojang public keys
- Request (GET)
https://api.minecraftservices.com/publickeys
- Response
- Template:Nbt Root tag
- Template:Nbt: A list of public keys used for verifying player properties (such as skin and cape textures), from e.g.
https://sessionserver.mojang.com/session/minecraft/profile/<UUID>?unsigned=false. A player property is considered valid by the client iff signed by any of these keys.- Template:Nbt: Base64-encoded DER public key.
- Template:Nbt: A list of public keys used for verifying player public keys, e.g. from
https://api.minecraftservices.com/player/certificates. A player certificate is considered valid by the client iff signed by any of these keys.- Template:Nbt: Base64-encoded DER public key.
- Template:Nbt: A list of public keys used for verifying authentication tokens, like the JWT access token used in the
AuthorizationHeader in most requests to the Minecraft API.- Template:Nbt: Base64-encoded DER public key.
- Template:Nbt: A list of public keys used for verifying player properties (such as skin and cape textures), from e.g.
Friends list
The friends list was introduced in Minecraft Java Edition 26.2 Snapshot 7. The API and client behavior are as such unstable and likely to change.
These endpoints are located on https://api.minecraftservices.com, and all require the Authorization header to be provided in the request with a value of Bearer <Minecraft access token>. An HTTP 401 error is returned if the token is missing or invalid.
Get list of friends and friend requests
- Request (GET)
/friends
You may send an optional If-None-Match request header. See below for details.
- Response
- Template:Nbt: Root tag.
- Template:Nbt: Accepted friends.
- Template:Nbt: An accepted friend.
- Template:Nbt: UUID of player.
- Template:Nbt: Java Edition profile name of player.
- Template:Nbt: An accepted friend.
- Template:Nbt: Friend requests received from others.
- Template:Nbt: A friend request.
- Template:Nbt: UUID of player.
- Template:Nbt: Java Edition profile name of player.
- Template:Nbt: A friend request.
- Template:Nbt: Friend requests sent to others.
- Template:Nbt: A friend request.
- Template:Nbt: UUID of player.
- Template:Nbt: Java Edition profile name of player.
- Template:Nbt: A friend request.
- Template:Nbt: If the friends list is empty.
trueif the player has 0 friends and 0 incoming or outgoing friend requests.
- Template:Nbt: Accepted friends.
The response has an ETag response header, which you may save and reuse as the value for filling the If-None-Match request header in further requests.
If the friends list has not changed since the last request, and the value of the If-None-Match request header matches the value of the ETag response header in the last request, Mojang will return a HTTP 304 Not Modified response.
- Example
<syntaxhighlight lang="json"> {
"friends": [
{
"profileId": "61699b2ed3274a019f1e0ea8c3f06bc6",
"name": "Dinnerbone"
}
],
"incomingRequests": [
{
"profileId": "853c80ef3c3749fdaa49938b674adae6",
"name": "jeb_"
}
],
"outgoingRequests": [
{
"profileId": "069a79f444e94726a5befca90e38aaf5",
"name": "Notch"
}
],
"empty": false
} </syntaxhighlight>
Friend management
- Request (PUT)
/friends
- Payload
- Template:Nbt
- Template:Nbt: Profile name of the player to add or remove. Optional.
- Template:Nbt: UUID of the player to add or remove. Could be UUID with or without dashes Optional.
- Template:Nbt: Action to perform. Values:
ADDfor sending or accepting friend requests,REMOVEfor removing friends or denying friend requests.
name and profileId are optional, but at least one must be provided. If both are present, profileId will be prioritized.
- Example
<syntaxhighlight lang="json"> {
"profileId": "069a79f444e94726a5befca90e38aaf5", "updateType": "ADD"
} </syntaxhighlight>
- Response
Identical to #Get list of friends and friend requests.
- Error
- Template:Nbt
- Template:Nbt: The requested path. For this API Endpoint, it will always be
/friends. - Template:Nbt:
- Template:Nbt: Error code. See the table below.
- Template:Nbt: Human-readable error message.
- Template:Nbt: The requested path. For this API Endpoint, it will always be
| HTTP status code | Template:Cd | Template:Cd | Explanation |
|---|---|---|---|
| 400 | UNKNOWN_PROFILE | Name or profile does not exist | |
| CANNOT_ADD_SELF | Cannot add yourself as friend | ||
| NOT_FRIENDS | Not friend with Template:Cd cannot remove | ||
| ALREADY_FRIENDS | Already friend with Template:Cd | ||
| INVALID_JSON | Depends on request body | All required arguments presents in request body, but somehow cannot be decoded (e.g. wrong enum values, invalid UUID) | |
| CONSTRAINT_VIOLATION | Depends on request body | Some required arguments are missing | |
| 403 | INVITE_REJECTED | User does not have friends enabled or accept invites |
Presence
This API is used for both querying friends' presences and reporting your current presence status. There is no standalone API for querying friends' presences.
- Request (POST)
/presence
- Payload
- Template:Nbt
- Template:Nbt: Your current presence status. See the table below for all available statuses.
- Template:Nbt: Information relating to peer-to-peer multiplayer invites. Should be omitted in certain situations, see below.
- Template:Nbt: Currently the game client always sends
null. See below. - Template:Nbt: List of invited players. Optional.
- Template:Nbt: UUID of a invited player. Could be UUID with or without dashes.
- Template:Nbt: Currently the game client always sends
For all presence statuses you may choose to omit the entire joinInfo object, even while reporting a PLAYING_HOSTED_SERVER status. Your friends' game client won't show "Ask to join" button in this situation. This is likely unintended behavior, and is subject to change in the near future.
To report an OFFLINE, ONLINE, PLAYING_OFFLINE or PLAYING_SERVER status, you must have a string (1 <= length <= 256) or a numeric joinInfo.value present or omit the entire joinInfo object, otherwise Mojang will return a HTTP 400 Bad Request response.
To report a PLAYING_HOSTED_SERVER status, you must have a null joinInfo.value present or omit the entire joinInfo object, otherwise Mojang will return a HTTP 400 Bad Request response.
To report a PLAYING_REALMS status, you must have a numeric joinInfo.value present or omit the entire joinInfo object, otherwise Mojang will return a HTTP 400 Bad Request response.
You can add non-friends (even dummy UUIDs, such as all zeroes) to joinInfo.invites, and Mojang will not return an error. However, since you're not friends, you won't appear in their friends list, so they won't receive your peer-to-peer multiplayer invite.
- Response
- Template:Nbt
- Template:Nbt: List of presence statuses. Only online friends will be included within this list.
- Template:Nbt: A presence status.
- Template:Nbt: UUID of the player, with dashes.
- Template:Nbt: PMID used in peer-to-peer multiplayer, encoded in player's Access Token. UUID with dashes.
- Template:Nbt: Player's presence status. See the table below for all available statuses.
- Template:Nbt: May be omitted in certain situations, see below.
- Template:Nbt: Typically the same as the PMID, but can also be other values.
- Template:Nbt: Whether you are invited to join the peer-to-peer multiplayer session.
- Template:Nbt: Timestamp of the most recent presence report from this player.
- Template:Nbt: A presence status.
- Template:Nbt: List of presence statuses. Only online friends will be included within this list.
joinInfo will be omitted if your friends omitted it when reporting their presence. This should not happen in vanilla Minecraft clients.
For most cases joinInfo.value should be same as the PMID, but could also be something else if your friends send a non-null value while reporting their presences. This shouldn't happen in vanilla Minecraft clients. The meaning of this key is unclear, since the game client ignores it.
joinInfo.invited could be true when status is ONLINE. This means your friend set "Presence: LIMITED" in their settings, which restricts the status to reporting either ONLINE or OFFLINE, while still allowing them to invite others to join their peer-to-peer multiplayer session.
Currently, the game client doesn't seem to report the OFFLINE status before shutting down. After 10 minutes the server will consider the player offline due to their lack of presence updates.
This API Endpoint will always return the latest presence status reported by your friends. It means, if you have two game instance running at the same time, a race condition may occur since both instances are trying to report their presence status, and which status your friends will see depends on when their client report their presence status.
- All available presence statuses
| Template:Cd | Text shows on UI | Explanation | Comment |
|---|---|---|---|
| OFFLINE | Offline | The player is offline | Offline players won't appear in their friends' presence responses |
| ONLINE | Online | The player is online but not playing any world or server (e.g. idling on the main menu, tweaking settings, etc.) | If the player set "Presence: LIMITED" in settings, the game will always report this |
| PLAYING_OFFLINE | Online (World) | The player is playing a world which isn't open to peer-to-peer multiplayer (Multiplayer set to "OFF" or "LAN") | |
| PLAYING_HOSTED_SERVER | Online (World) | The player is playing a world which is open to peer-to-peer multiplayer and allow others to ask to join | There will be an "Ask to join" button |
| PLAYING_REALMS | Online (Realms) | The player is playing a Realms server | Currently the game client will not report this |
| PLAYING_SERVER | Online (Server) | The player is playing on a third-party server | Currently the game client will not report this |
- Error
Nearly identical to #Friends management, with only INVALID_JSON and CONSTRAINT_VIOLATION present. If you have joinInfo.value present but violate the rules mentioned above, Mojang will return a HTTP 400 Bad Request error with only path and errorMessage.
- Example request
<syntaxhighlight lang="json"> {
"status": "PLAYING_HOSTED_SERVER",
"joinInfo": {
"value": null,
"invites": [
"069a79f4-44e9-4726-a5be-fca90e38aaf5",
"853c80ef-3c37-49fd-aa49-938b674adae6",
"61699b2e-d327-4a01-9f1e-0ea8c3f06bc6"
]
}
} </syntaxhighlight>
- Example response
<syntaxhighlight lang="json"> {
"presence": [
{
"profileId": "069a79f4-44e9-4726-a5be-fca90e38aaf5",
"pmid": "c41da2ff-0874-4bbe-a28b-d122bc467b1f",
"status": "ONLINE",
"lastUpdated": "2026-05-15T19:25:58Z"
},
{
"profileId": "853c80ef-3c37-49fd-aa49-938b674adae6",
"pmid": "87c83220-965c-4d65-867f-8718f5ba04a6",
"status": "ONLINE",
"joinInfo": { "value": "87c83220-965c-4d65-867f-8718f5ba04a6", "invited": true },
"lastUpdated": "2026-05-15T19:26:12Z" },
{
"profileId": "61699b2e-d327-4a01-9f1e-0ea8c3f06bc6",
"pmid": "f289feed-30ab-4039-bef8-5752ea9d2c1c",
"status": "PLAYING_HOSTED_SERVER",
"joinInfo": { "value": "f289feed-30ab-4039-bef8-5752ea9d2c1c", "invited": false },
"lastUpdated": "2026-05-15T19:26:32Z" } ]
} </syntaxhighlight>
Peer-to-peer multiplayer networking
Peer-to-peer (P2P) multiplayer networking was introduced in Minecraft:Java Edition 26.2 Snapshot 7 and removed in Minecraft:Java Edition 26.2 Pre-Release 1. It allows a host's singleplayer world to be joinable by friends over Peer-to-Peer WebRTC data channels. A Mojang-hosted signaling service handles the handshake, gameplay traffic flows peer-to-peer (using ICE). The protocol described below is unstable and likely to change.
General Properties:
- The transport is DTLS-encrypted at the WebRTC layer; the client treats the resulting channel as a secure transport (
Connection.isSecureTransport() == true). The Minecraft in-protocol AES encryption is skipped, but the standard Mojang online-mode handshake (encryption request, key packet, YggdrasilhasJoinedServercheck) is still performed, without adding the Encryption/Decryption layers. - Both peers must be friends with each other (see #Friends list).
- Only one handshake is permitted per client and host at a time.
- The integrated server, when published peer-to-peer, forces online mode on regardless of any singleplayer-side configuration. The guest's identity is verified twice: once at signaling (via the PMID) and at the normal Minecraft login sequence.
Identification
The signaling service routes messages by the PMID. A friend's PMID is obtained from the pmid field of their entry in their presence response.
When a peer-to-peer connection completes, the host resolves the guest's profile UUID locally from the PMID via its presence cache and pins it to the connection. The standard ServerboundHelloPacket profile UUID sent from the guest is ignored; the server obtains the guest's profile UUID via the normal login flow, and rejects the connection if the result does not match the UUID from its PMID presence cache. See #Full Connection sequence below.
Authentication uses the same Mojang access token as the rest of the Mojang API, but is sent in the x-mojangauth HTTP header rather than as a Bearer token.
The client also includes two stable identifiers for every request:
Session-Id: a UUID created once at startup. Sent on every subsequent request.Request-Id: a fresh UUID generated per request.
For each join attempt a third identifier is used:
sessionId: a fresh UUID generated by the guest. EveryFriendJoinandWebRtcsignaling message belonging to that join attempt includes this id. Reusing an old session id will be rejected by the host.
Signaling service
Environment
The client selects one of two environments using the signaling.environment environment variable, falling back to a JVM system property of the same name, and finally defaulting to PRODUCTION.
| Template:Cd | Base URL |
|---|---|
stage / staging
|
https://signaling-afd.stage-6fd5f759.franchise.minecraft-services.net
|
prod / production (default)
|
https://signaling-afd.franchise.minecraft-services.net
|
The staging URL does not work and produces an Azure Front Door Service 404 Not found page<ref>https://signaling-afd.stage-6fd5f759.franchise.minecraft-services.net</ref>.
Discover the signaling WebSocket URL
- Request (GET)
/api/v1.0/configuration/java
Headers:
x-mojangauth: Minecraft access token. Required.Session-Id: Could be any non-empty value. Optional. See below.Request-Id: Fresh per-request UUID with or without dashes, but could be also any non-empty value. Optional. See below.
Both Session-Id and Request-Id can be any non-empty values or even be omitted. The server may behaves differently but it won't return errors. See below for details.
- Response
- Template:Nbt
- Template:Nbt:
- Template:Nbt: WebSocket endpoint, beginning with
wss://…. - Template:Nbt: A time in
hh:mm:ssformat, which tells the client how long to wait between sending ping messages.
- Template:Nbt: WebSocket endpoint, beginning with
- Template:Nbt:
The response will always contains a Request-Id header, which is a UUID without dashes matches the one you send in the request. However, if you omitted it in the request or filled it with other values cannot be recognized as UUID, it will be a new UUID without dashes generated by the server itself.
The response may contains a Session-Id header matches the one you send in the request as-is, or will also omit it if you omitted it in the request.
result.signalingUri may vary by multiple reasons, such as data encoded in access token or where you send the request. It doesn't matter if you and your friends connect to different signaling servers, although P2P multiplayer session invites and join requests are transferred on the signaling server, players connect to differnet signaling servers could still receive invites or join requests.
For reference, result.signalingUri currently follows the format wss://signal-{location}.franchise.minecraft-services.net, where {location} corresponds to one of the following Microsoft Azure region identifiers:
brazilsouthcentralindiaeastasiaeastus2mexicocentralnortheuropewesteuropewestus2
These shouldn't be hardcoded, as they are subject to change in the future.
- Example
<syntaxhighlight lang="json"> {
"result": {
"signalingUri": "wss://signal-westeurope.franchise.minecraft-services.net",
"pingFrequency": "00:01:00"
}
} </syntaxhighlight>
The client caches the signalingUri for 5 minutes.
Open the signaling WebSocket
- Request
GET {signalingUri}/ws/v1.0/messaging/connect/java with a WebSocket upgrade, using the same three headers (x-mojangauth, Session-Id, Request-Id).
Once the connection is open:
- The client sends a
System_Ping_v1_0JSON-RPC notification every 50 seconds. The server is expected to reply with aSystem_Pong_v1_0notification, which the client otherwise ignores. ThepingFrequencyfrom the discovery request is ignored by the client. - The server may push
Signaling_ReceiveMessage_v1_0requests to the client at any time.
If the WebSocket disconnects, or a connection attempt fails, and the client still needs signaling, it schedules another connection attempt after a random delay. The upper bound starts at 1 second and doubles after each failed attempt, capped at 30 seconds; the actual delay is uniformly chosen below that bound. If the client no longer needs signaling, it stays disconnected and the retry counter is reset. The counter is also reset after a successful signaling connection.
The WebSocket server disconnects the client automatically after 120 seconds of inactivity, or after 60 seconds if no data is sent since the connection established. In both cases, the connection is terminated forcefully with close code 1006 (Abnormal Closure). This is applicable for all messages and isn't limited to System_Ping_v1_0 messages.
WebSocket Framing
The WebSocket protocol is JSON-RPC 2.0 over text frames. The framing rules used by the client are:
- One JSON-RPC envelope per text message. Multi-frame messages are accumulated until the WebSocket flags the last frame.
- The total size of a single message is capped at 65 536 bytes. Oversize messages are dropped with a warning.
idmust be an integer. The game client uses an increasing integer starting at1, the signaling server uses an increasing integer starting at2. The response must have the identicalid. See below for details.- Unknown server-to-client methods are answered with the error
{"code":-32601,"message":"Method not found"}(METHOD_NOT_FOUND). - Handler exceptions on incoming requests are answered with a JSON-RPC error
{"code":-32603,"message":"Internal error"}(INTERNAL_ERROR).
The signaling server actually accept any id including string, number or even null, but cannot be boolean. Sending a WebSocket frame with a boolean id will cause the signaling server to immediately terminate the connection with close code 1000 (Normal Closure).
Although the signaling servers accept multiple data types for id, the game client strictly requires an integer id. If the signaling servers request the client with a non-integer id, this request will be dropped by the client.
For System_Ping_v1_0 requests sent by the client, the signaling server will just treat it as a notification, disregarding whether id was presented or not. The signaling server won't respond to it since it treats the request as a notification, instead, it will immediately send the client a System_Pong_v1_0 request with id presented, while the game client doesn't really care about whether the server requested it, and also treats it as a notification which will never be responded. The only exception is the following one right below.
In most cases, id can be freely reused. However, if the client send an id used in the previous System_Ping_v1_0 requests, the signaling server will respond with an InternalServerError error, and the next time you reuse this id again, the server will behave normally.
The server-to-client Signaling_ReceiveMessage_v1_0 requests are delivered sequentially by the signaling service. After the server sends a Signaling_ReceiveMessage_v1_0 request with an id, it waits for the client to send either a success response or an error response with the same id before sending the next server-to-client request on that WebSocket connection. If the client does not respond, later server-to-client requests are queued server-side and will not be delivered until the pending request is answered or the connection is closed.
Standard JSON-RPC 2.0 envelopes:
<syntaxhighlight lang="json"> // Request (id must be a JSON integer, parms can be omitted if no params are required) { "jsonrpc": "2.0", "id": 1, "method": "<name>", "params": [...] }
// Notification (id omitted, won't be responded unless error occurred) { "jsonrpc": "2.0", "method": "<name>", "params": [...] }
// Success response { "jsonrpc": "2.0", "id": 1, "result": <any> }
// Error response for backend error {
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32601, // Numeric error code defined in JSON-RPC 2.0 spec
"message": "...",
"data": {
"Namespace": "ServiceRuntime",
"Code": "<Name for the error code field>",
"Message": "<Same content as in 'message'>",
"CustomData": {},
"StackTrace": null,
"InnerError": null
}
}
}
// Error response for JSON-RPC gateway error {
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "...",
"data": { // may be omitted
"type": "...",
"message": "...",
"stack": null,
"code": -2146233088,
"inner": { ... } // nested data object or null
}
}
}
</syntaxhighlight>"jsonrpc": "2.0" is actually optional for both signaling server and game client, but it's recommended to present to respect the JSON-RPC 2.0 specification.
If the client send a WebSocket frame that cannot be recognized as a JSON-RPC request (including sending a response containing a not requested id), the signaling server will close the connection with close code 1000 (Normal Closure).
Methods
| Direction | Method | Params | Result |
|---|---|---|---|
| C→S notification | System_Ping_v1_0
|
[]
|
— |
| S→C notification | System_Pong_v1_0
|
(ignored) | — |
| C→S request | Signaling_TurnAuth_v1_0
|
[]
|
TurnAuthResult (see #TURN credentials)
|
| C→S request | Signaling_SendClientMessage_v1_0
|
{ messageId, toPlayerId, message }
|
null / {} (client ignores on success)
|
| S→C request | Signaling_ReceiveMessage_v1_0
|
[{ From, Message, Id }]
|
{} (ACK only if id is present)
|
The third parameter (message) of Signaling_SendClientMessage_v1_0 is a stringified JSON value, not a nested JSON object. The signaling service treats it opaquely and forwards it verbatim as the Message field of Signaling_ReceiveMessage_v1_0 on the recipient's side.
Server-side errors
On C→S requests, the server may return a JSON-RPC error whose data is an object. The client checks an optional string field data.Code:
| Named Error code | Numeric Error code | Error Message | Explanation |
|---|---|---|---|
MissingOrExpiredIdentity
|
-32000 (Invalid request)
|
Player not registered with the service. | The target PMID is Invalid |
InternalServerError
|
-32000 (Invalid request)
|
Depends on request | An internal server error occurred |
| (not presented) | -32601 (Method not found)
|
No method by the name '<method name>' is found. | |
| (not presented) | -32602 (Invalid params)
|
Unable to find method '<method name>/<presented params count>' on {no object} for the following reasons: An argument was not supplied for a required parameter. | Some required params are missing |
| (not presented) | -32602 (Invalid params)
|
Unable to find method '<method name>/<presented params count>' on {no object} for the following reasons: depends on params presented | Some params presented cannot be recognized |
TURN credentials
Before creating a peer connection, the client fetches TURN authentication credentials from the WebSocket connection.
- Request
<syntaxhighlight lang="json"> {
"jsonrpc": "2.0", "id": 1, "method": "Signaling_TurnAuth_v1_0", "params": []
} </syntaxhighlight>
- Response (
result)
- Template:Nbt
- Template:Nbt: Time until the credentials should be refreshed, in seconds.
- Template:Nbt: One or more TURN/STUN server entries.
- Template:Nbt:
- Template:Nbt: TURN username.
- Template:Nbt: TURN password (long-term credential).
- Template:Nbt: List of STUN/TURN URLs.
- Template:Nbt: TURN/STUN URL (e.g.
stun:...,turn:...)
- Template:Nbt: TURN/STUN URL (e.g.
- Template:Nbt:
The result will be cached by the game client for 60 seconds in 26.2-snapshot-7 or ExpirationInSeconds - 60 seconds in 26.2-snapshot-8.
- Example
<syntaxhighlight lang="json"> {
"jsonrpc": "2.0",
"id": 1,
"result": {
"ExpirationInSeconds": 604799,
"TurnAuthServers": [
{
"Username": "<long base64 string>",
"Password": "<base64 string>",
"Urls": [
"stun:relay.communication.microsoft.com:3478",
"turn:relay.communication.microsoft.com:3478"
]
}
]
}
} </syntaxhighlight>
The client constructs a single RTCIceServer by taking the Username and Password of the first entry and the union of Urls from all entries. The peer-connection configuration disables TCP candidates and enables IPv6 (including on Wi-Fi).
SignalingMessage format
All application-level signaling traffic between two peers is exchanged as SignalingMessage envelopes, encoded as a string in the third parameter of Signaling_SendClientMessage_v1_0 (outbound) or in the Message field of Signaling_ReceiveMessage_v1_0 (inbound).
- Envelope
- Template:Nbt
- Template:Nbt: Message kind. One of
JOIN_REQUEST,JOIN_ACCEPTED,JOIN_REJECTED,INVITE_DECLINED,OFFER,ANSWER,ICE_CANDIDATE. - Template:Nbt: UUID identifying the join attempt this message belongs to. Always present.
- Template:Nbt: Full SDP body. Required for
OFFERandANSWER; omitted otherwise. May also carry the candidate string in the legacyICE_CANDIDATEfallback (see below). - Template:Nbt: ICE candidate. Required for
ICE_CANDIDATE; omitted otherwise.- Template:Nbt: Candidate attribute line, e.g.
candidate:842163049 1 udp 2122260223 192.0.2.1 49152 typ host. - Template:Nbt: SDP Media stream Id. Optional and defaults to
"0". - Template:Nbt: SDP m-line index, e.g.
0.
- Template:Nbt: Candidate attribute line, e.g.
- Template:Nbt: Message kind. One of
If an ICE_CANDIDATE message arrives without a valid iceCandidate object, the decoder falls back to using the sdp field as the candidate attribute, with the defaults sdpMid="0" and sdpMLineIndex=0.
Message types:
<syntaxhighlight lang="json"> // JOIN_REQUEST { "type": "JOIN_REQUEST", "sessionId": "6b8b4567-327d-4ee8-90c2-0d8c1f9f1d9e" }
// JOIN_ACCEPTED { "type": "JOIN_ACCEPTED", "sessionId": "6b8b4567-327d-4ee8-90c2-0d8c1f9f1d9e" }
// JOIN_REJECTED { "type": "JOIN_REJECTED", "sessionId": "6b8b4567-327d-4ee8-90c2-0d8c1f9f1d9e" }
// INVITE_DECLINED { "type": "INVITE_DECLINED","sessionId": "a3f1c8d2-1111-2222-3333-444455556666" }
// OFFER { "type": "OFFER", "sessionId": "6b8b4567-...", "sdp": "v=0\r\no=- ...\r\n" }
// ANSWER { "type": "ANSWER", "sessionId": "6b8b4567-...", "sdp": "v=0\r\no=- ...\r\n" }
// ICE_CANDIDATE {
"type": "ICE_CANDIDATE",
"sessionId": "6b8b4567-...",
"iceCandidate": {
"candidate": "candidate:842163049 1 udp 2122260223 192.0.2.1 49152 typ host",
"sdpMid": "0",
"sdpMLineIndex": 0
}
} </syntaxhighlight>
Application semantics
| Template:Cd | Direction | Meaning |
|---|---|---|
JOIN_REQUEST
|
Guest → Host | Request to join the hosted world. Sent by the joiner with a fresh sessionId.
|
JOIN_ACCEPTED
|
Host → Guest | The host accepted the request. The guest must follow up by initiating the WebRTC handshake using the same sessionId.
|
JOIN_REJECTED
|
Host → Guest | The host rejected the request due to any reason. |
INVITE_DECLINED
|
Guest → Host | The guest declined an invite that the host had previously listed in their joinInfo.invites. The host clears the corresponding presence invite.
|
OFFER
|
Initiator (guest) → Host | WebRTC SDP offer for the handshake identified by sessionId.
|
ANSWER
|
Host → Initiator (guest) | WebRTC SDP answer for the same sessionId.
|
ICE_CANDIDATE
|
Either direction | ICE candidate produced by the local peer-connection. Always tagged with the same sessionId.
|
Host-side validation:
- If the host is not currently hosting a world, an incoming
JOIN_REQUESTis rejected withJOIN_REJECTEDimmediately. - If the host and the sender are not mutual friends, the
JOIN_REQUESTis silently dropped, noJOIN_REJECTEDis sent. - If the sender's PMID is in the host's outstanding presence
joinInfo.invites, the host auto-accepts (sendsJOIN_ACCEPTED). - Otherwise, the request waits to be Accepted or Rejected by the user.
The host filters incoming OFFER messages against the sender PMID and sessionId pair it previously sent JOIN_ACCEPTED for. Offers that do not match (no prior accept, wrong session id) are silently dropped.
Sending and receiving signaling messages
Sending (Signaling_SendClientMessage_v1_0)
Every outbound application message is wrapped as follows. The third positional parameter is the SignalingMessage envelope serialised to a JSON string, not a nested JSON object.
<syntaxhighlight lang="json"> {
"jsonrpc": "2.0",
"id": 2,
"method": "Signaling_SendClientMessage_v1_0",
"params": {
"messageId": "5c2a87a0-2bd1-4b9f-9c50-1c2d5b4f8a91", // could be UUID with dashes or null, if it's null, signaling server server will generate a random UUID as messageId
"toPlayerId": "069a79f4-44e9-4726-a5be-fca90e38aaf5", // PMID of recipient
"message": "{\"type\":\"JOIN_REQUEST\",\"sessionId\":\"6b8b4567-327d-4ee8-90c2-0d8c1f9f1d9e\"}" // message to send
}
} </syntaxhighlight>
On success the server responds with a JSON-RPC result that the client ignores.
If the client requests the signaling server Signaling_SendClientMessage_v1_0 but the PMID didn't connect to the signaling server at least once, the signaling server will return a MissingOrExpiredIdentity error.
Receiving (Signaling_ReceiveMessage_v1_0)
Inbound application messages arrive as server-initiated JSON-RPC requests:
<syntaxhighlight lang="json"> {
"jsonrpc": "2.0",
"id": 17,
"method": "Signaling_ReceiveMessage_v1_0",
"params": {
"From": "61699b2e-d327-4a01-9f1e-0ea8c3f06bc6", // PMID of sender
"Message": "{\"type\":\"JOIN_REQUEST\",\"sessionId\":\"6b8b4567-327d-4ee8-90c2-0d8c1f9f1d9e\"}", // message received
"Id": "5c2a87a0-2bd1-4b9f-9c50-1c2d5b4f8a91" // messageId
}
} </syntaxhighlight>
If the request carries a numeric id, the client must immediately replies with {"jsonrpc":"2.0","id":<id>,"result":{}} as an ACK, otherwise the signaling service will not deliver later Signaling_ReceiveMessage_v1_0 requests on the same WebSocket connection.
If the client requests the signaling server Signaling_SendClientMessage_v1_0 with a valid PMID but the message somehow cannot be delivered, the signaling server will send the client a Signaling_ReceiveMessage_v1_0 request immediately with opposite's PlayFab ID (a 16 characters length hex string) presented in params[0].From, along with a service envelope describe below presented in params[0].Message. The PlayFab ID params[0].From looks like a bug and may subject to changed in future.
Service envelope (delivery failure)
The inner Message may also be a service envelope indicating a delivery failure rather than a peer message. A service envelope is a JSON object with a top-level numeric Code and a Message field. It does not include a type field:
<syntaxhighlight lang="json"> { "Code": 1, "Message": "Player 069a79f4-... is not reachable" } </syntaxhighlight>
| Template:Cd | Symbolic name | Meaning |
|---|---|---|
| 1 | PLAYER_UNREACHABLE
|
The target PMID is not currently connected to signaling. |
| 2 | MESSAGE_DELIVERY_FAILED
|
The signaling service could not deliver the message. |
| 3 | TURN_AUTH_FAILED
|
Server-side TURN auth failure for the underlying flow. |
Full Connection sequence
The full sequence for guest A joining host B
- A sends
Signaling_TurnAuth_v1_0to obtain TURN credentials and constructs anRTCIceServer. - A generates a fresh
sessionIdand sendsJOIN_REQUESTtargeted at B's PMID, wrapped inSignaling_SendClientMessage_v1_0. The join request expires after one minute on As side. - B's client receives
JOIN_REQUESTviaSignaling_ReceiveMessage_v1_0. After friend and hosting checks, it either:- Auto-accepts (if A is in B's outstanding presence
invites); or - Silently drops (if A is not a friend); or
- Sends
JOIN_REJECTEDimmediately (if B is not hosting); or - Waits for player approval before accepting or rejecting the join request.
- Auto-accepts (if A is in B's outstanding presence
- On acceptance, B records (A's
PMIDandsessionId), and sendsJOIN_ACCEPTEDwith the samesessionId. - A receives
JOIN_ACCEPTED, builds the peer connection, creates a data channel labelled"minecraft", and sends the SDP asOFFER. A also starts a 10-second handshake timer. - As local ICE candidates appear on A, each is sent as
ICE_CANDIDATEwith the samesessionId. - B receives
OFFER, validates that(from, sessionId)matches, builds its own peer connection, generates the SDP answer, and sends it asANSWER. B starts its own 10-second timer and sends its ICE candidates back asICE_CANDIDATE. - Both sides add incoming
ICE_CANDIDATEmessages intoaddIceCandidate. When the data channel reaches theOPENstate, the handshake is considered complete and both 10-second timers are cancelled. - A wraps the open data channel as a Netty channel and runs the standard Minecraft login flow over it: sends
ServerboundHelloPacketannouncing its profile name and profile UUID. The host does perform the standard online-mode handshake (ClientboundHelloPacket→ServerboundKeyPacket→ MojanghasJoinedServercheck); the only difference from a normal TCP login is that the negotiated symmetric key is not applied to the channel (the connection is already DTLS-encrypted, sosetEncryptionKeyis skipped). The auth-derived profile UUID is then checked against the connection's pinnedintendedProfileId; if they do not match, the host disconnects with a Connection failed message. - B wraps the open data channel similarly and hands it to its integrated server as a pre-authenticated connection bound to A's profile UUID (resolved from A's PMID).
- A and B both check whether they still need signaling; if not, the WebSocket is closed.
RTCConfiguration used by the client
For both sides, the configuration is identical:
<syntaxhighlight lang="java"> RTCConfiguration cfg = new RTCConfiguration(); cfg.iceServers.add(turnAuth); cfg.portAllocatorConfig
.setDisableTcp(true) .setEnableIpv6(true) .setEnableIpv6OnWifi(true);
</syntaxhighlight>
Data channel parameters (initiator only)
<syntaxhighlight lang="java"> RTCDataChannelInit init = new RTCDataChannelInit(); init.ordered = true; init.maxRetransmits = -1; init.priority = RTCPriorityType.HIGH; peerConnection.createDataChannel("minecraft", init); </syntaxhighlight>
The responder does not create its own data channel; it receives the initiator's channel via onDataChannel.
Hand-off to standard Minecraft Protocol
Once the data channel reaches OPEN:
- Guest side: constructs a Netty channel adapter over the data channel, builds a Minecraft serverbound play connection using
"rtc-peer"as the placeholder host name and port0, sends a singleServerboundHelloPacket(profileName, profileId). From that point on the standard login, configuration and play protocol runs over the data channel. - Host side: constructs the same Netty channel adapter and passes it, together with the guest's profile UUID (resolved from the PMID), into the integrated server's authenticated channels. The server thereafter treats the connection as a normally-authenticated, connected player.
Outbound chunking and backpressure
The client and server enforce the following on outbound data-channel writes:
- Every Netty write that exceeds 256 KiB is split into 256 KiB chunks before being sent.
- When the WebRTC
bufferedAmounton the data channel exceeds 1 MiB, the Netty channel is marked non-writable and the application is expected to back off. - When
bufferedAmountfalls back below 256 KiB, the Netty channel is marked writable again and writes resume.
History
References
This article is partially adapted from Mojang API on wiki.vg.
Examples
- C# - Full API wrapper
- C# - Full API wrapper with Mojang/Microsoft Authentication
- Dart - Almost full API wrapper with Mojang Authentication
- Go - Full API wrapper
- Go - UUIDs or names to profiles with skins, capes and name histories
- Python - Full API Wrapper. Also supports authentication & parts of the Minecraft website
- Python - Pymojang is a full wrapper around de Mojang API and Mojang Authentication API. Also support RCON, Query and Server List Ping
- Python - Full API wrapper (not updated since 2018)
- Python - UUIDs or names to profiles (not updated since 2018)
- PHP - Complete Mojang's API wrapper
- PHP - Almost full API wrapper with Mojang Authentication. Also support head rendering
- PHP - UUIDs or names to profiles with skins, heads and name histories
- PHP - UUIDs to names
- PHP - UUIDs to names, names to uuids
- Java - Almost full API Wrapper
- Java - Almost full API Wrapper with auth
- Java - Almost full API with Mojang Authentication and can also work as a console Application.
- JavaScript - UUIDs or names to profiles with skins, capes and name histories
- JavaScript/TypeScript - Almost full API wrapper
References
Template:Navbox Java Edition technical Template:License wiki.vg
Minecraft:de:Mojang API Minecraft:lzh:魔贊卯口 Minecraft:zh:Mojang API