Minecraft: Bedrock Edition featured servers API documentation
Minecraft Bedrock Featured Servers API
This document describes how Minecraft Bedrock Edition fetches the partner servers shown on the Servers tab of the play screen ("Featured experiences" and "Creator experiences"), how to authenticate against the services involved, the exact request and response formats, and how to join a listed server.
Everything here was verified against the live production services on 2026-09-27 using game version 1.26.51 (network protocol 2193). All sample responses below were captured on that date. Tokens and session tickets are truncated.
Table of contents
1. Overview
The server list is not stored in the game. The client builds it at runtime from Mojang's gatherings service, which serves a precomputed "discovery blob" of PlayFab catalog items with content type 3PP_V2.0 (third party partner, version 2.0).
Four services take part:
| Service | Host (prod) | Purpose |
|---|---|---|
| Discovery | client.discovery.minecraft-services.net | Maps service names to base URLs for a given game build. No auth. |
| PlayFab | 20ca2.playfabapi.com | Issues a PlayFab session ticket. Title id 20CA2. |
| Authorization | authorization.franchise.minecraft-services.net | Exchanges the PlayFab ticket for an MCToken. |
| Gatherings | gatherings-secondary.franchise.minecraft-services.net | Serves the server list and resolves joins. |
Images are served from two CDNs with no authentication:
cdn.gatherings.franchise.minecraft-services.netxforgeassets001.xboxlive.com,xforgeassets002.xboxlive.com(and other numbered hosts)
No Microsoft account is required to read the list. An anonymous PlayFab login through LoginWithIOSDeviceID is accepted by every step up to and including joining.
2. Flow at a glance
GET https://client.discovery.minecraft-services.net/api/v1.0/discovery/MinecraftPE/builds/{gameVersion}
-> serviceEnvironments.auth.prod.serviceUri
-> serviceEnvironments.gatherings.prod.serviceUri
-> serviceEnvironments.auth.prod.playfabTitleId
POST https://{titleId lowercase}.playfabapi.com/Client/LoginWithIOSDeviceID
-> data.SessionTicket
POST {auth}/api/v1.0/session/start
-> result.authorizationHeader ("MCToken eyJ...")
POST {gatherings}/api/v2.0/discovery/blob/client Authorization: MCToken ...
-> data.Items[] (the servers)
GET {Images[].Url} (icons, banners, screenshots)
POST {gatherings}/api/v2.0/join/experience Authorization: MCToken ...
-> result.ipV4Address, result.port (where to connect)
3. Step 1: Service discovery
Resolves the base URL of every Minecraft service for a specific game build. The hosts in this document are what it returned on the capture date; always resolve them at runtime because Mojang moves services between hosts (note the -secondary suffixes).
Request
GET /api/v1.0/discovery/MinecraftPE/builds/1.26.51 HTTP/1.1 Host: client.discovery.minecraft-services.net Accept: application/json
- No authentication.
MinecraftPEis the client type. The build number is the game version asmajor.minor.patch.
Response (trimmed to the relevant entries)
{
"result": {
"serviceEnvironments": {
"auth": {
"prod": {
"serviceUri": "https://authorization.franchise.minecraft-services.net",
"issuer": "https://authorization.franchise.minecraft-services.net",
"playfabTitleId": "20CA2",
"eduPlayFabTitleId": "6955F"
}
},
"gatherings": {
"prod": {
"serviceUri": "https://gatherings-secondary.franchise.minecraft-services.net"
}
},
"cdn": {
"prod": {
"serviceUri": "https://cdn.gatherings.franchise.minecraft-services.net/public/"
}
},
"store": { "prod": { "serviceUri": "https://store.mktpl.minecraft-services.net", "playfabTitleId": "20CA2" } },
"persona": { "prod": { "serviceUri": "https://persona-secondary.franchise.minecraft-services.net", "playfabTitleId": "20CA2" } },
"signaling": { "prod": { "serviceUri": "wss://signaling-tm-westeurope.franchise.minecraft-services.net" } },
"safety": { "prod": { "serviceUri": "https://safety-secondary.franchise.minecraft-services.net" } },
"mpsas": { "prod": { "serviceUri": "https://secondary.allocation.multiplayer.minecraft-services.net" } },
"frontend": { "prod": { "serviceUri": "https://client.allocation.multiplayer.minecraft-services.net" } },
"multiplayer": { "prod": { "serviceUri": "https://secondary.multiplayer.minecraft-services.net" } },
"realmsfrontend": { "prod": { "serviceUri": "https://frontend.realms.minecraft-services.net" } }
}
}
}Fields used
| Path | Example | Use |
|---|---|---|
result.serviceEnvironments.auth.prod.serviceUri | https://authorization.franchise.minecraft-services.net | Base URL for step 3. |
result.serviceEnvironments.auth.prod.playfabTitleId | 20CA2 | PlayFab title for step 2. |
result.serviceEnvironments.gatherings.prod.serviceUri | https://gatherings-secondary.franchise.minecraft-services.net | Base URL for steps 4 and 5. |
result.serviceEnvironments.cdn.prod.serviceUri | https://cdn.gatherings.franchise.minecraft-services.net/public/ | Informational. Image URLs in the list are already absolute. |
4. Step 2: PlayFab login
Minecraft's PlayFab title is 20CA2. The host is the title id in lowercase.
Option A: anonymous (recommended for listing)
POST /Client/LoginWithIOSDeviceID HTTP/1.1
Host: 20ca2.playfabapi.com
Content-Type: application/json
{
"CreateAccount": true,
"TitleId": "20CA2",
"DeviceId": "0f5c3a1e-8b7d-4a2f-9c11-2e6d4b8a7f90",
"OS": "iOS"
}| Field | Type | Notes |
|---|---|---|
CreateAccount | bool | Must be true so a fresh device id creates a PlayFab player. |
TitleId | string | 20CA2. |
DeviceId | string | Any unique string. A random UUID v4 per launch works. Reusing one id keeps the same PlayFab player. |
OS | string | iOS. |
Option B: Xbox Live (signed in players)
POST /Client/LoginWithXbox HTTP/1.1
Host: 20ca2.playfabapi.com
Content-Type: application/json
{
"TitleId": "20CA2",
"CreateAccount": true,
"XboxToken": "XBL3.0 x={userHash};{xstsToken}"
}The XSTS token must be issued for the relying party http://playfab.xboxlive.com/.
Response (same shape for both options)
{
"code": 200,
"status": "OK",
"data": {
"SessionTicket": "5E7A...-...",
"PlayFabId": "A1B2C3D4E5F6",
"NewlyCreated": true,
"SettingsForUser": {
"NeedsAttribution": false,
"GatherDeviceInfo": true,
"GatherFocusInfo": true
},
"EntityToken": {
"EntityToken": "NHxhb...",
"TokenExpiration": "2026-09-28T15:10:54.000Z",
"Entity": { "Id": "...", "Type": "title_player_account", "TypeString": "title_player_account" }
},
"TreatmentAssignment": { "Variants": [], "Variables": [] }
}
}Only data.SessionTicket is needed for the next step.
Logins that are rejected
| Endpoint | Result |
|---|---|
Client/LoginWithCustomID | 403 NotAuthorizedByTitle (errorCode 1191, "Action not authorized by title"). Disabled for this title. |
5. Step 3: Minecraft services session (MCToken)
Exchanges the PlayFab session ticket for an MCToken, the bearer token every *.minecraft-services.net API expects.
Request
POST /api/v1.0/session/start HTTP/1.1
Host: authorization.franchise.minecraft-services.net
Content-Type: application/json
Accept: application/json
{
"user": {
"language": "en",
"languageCode": "en-US",
"regionCode": "US",
"token": "{SessionTicket}",
"tokenType": "PlayFab"
},
"device": {
"applicationType": "MinecraftPE",
"memory": "8589934592",
"id": "{random uuid}",
"gameVersion": "1.26.51",
"platform": "Windows10",
"playFabTitleId": "20CA2",
"storePlatform": "uwp.store",
"type": "Windows10"
}
}| Field | Notes |
|---|---|
user.token | PlayFab SessionTicket from step 2. |
user.tokenType | PlayFab. |
user.language, languageCode, regionCode | Locale of the session. Does not change the server list (see ). |
device.applicationType | MinecraftPE. |
device.memory | Device RAM in bytes, as a string. Any plausible value works. |
device.id | Random UUID. |
device.gameVersion | Should match the build used in discovery. |
device.platform, device.type | Windows10. |
device.storePlatform | uwp.store. |
device.playFabTitleId | 20CA2. |
Response
{
"result": {
"authorizationHeader": "MCToken eyJhbGciOiJS...",
"validUntil": "2026-09-27T19:10:54Z",
"issuedAt": "2026-09-27T15:10:54Z",
"treatments": [
"mc-gatherings-discovery-enable",
"mc-enable-gatherings-backend-for-server-list",
"mc-ab-new-play-screen-d",
"..."
],
"configurations": {
"minecraft": { "id": "Minecraft", "parameters": { "parties-travel-to-experiences": "true", "...": "..." } },
"mcLauncher": { "id": "MCLauncher", "parameters": { "...": "..." } }
},
"treatmentContext": "mc-rp-banner-fix:31154262;...",
"accountPermissions": 7
}
}| Field | Use |
|---|---|
result.authorizationHeader | The complete value for the Authorization header in steps 4 and 5, including the MCToken prefix. Send it verbatim. |
result.validUntil | Expiry. Observed lifetime is 4 hours. Refresh before it passes. |
result.treatments | Feature flags. mc-gatherings-discovery-enable and mc-enable-gatherings-backend-for-server-list are the flags that make the real client use the gatherings backend for its server list. |
Signed in players can get the same token with an Xbox based PlayFab ticket. In Falcon Network that is MinecraftAuthentication::requestServiceToken().
6. Step 4: Fetch the server list
Request
POST /api/v2.0/discovery/blob/client HTTP/1.1
Host: gatherings-secondary.franchise.minecraft-services.net
Content-Type: application/json
Accept: application/json
Authorization: MCToken eyJhbGciOiJS...
{
"count": true,
"filter": "(contentType eq '3PP_V2.0') and platforms/any(tp: tp eq 'android.googleplay') and platforms/any(tp: tp eq 'title.bedrockvanilla')",
"orderBy": "startDate desc",
"scid": "4fc10100-5f7a-4470-899b-280835760c07",
"select": "images",
"top": 75
}| Field | Value sent by the game | Observed effect |
|---|---|---|
count | true | None observed. Count is always present. |
filter | OData filter on content type and platforms | Ignored. Dropping the platform clauses returns the same 13 items. |
orderBy | startDate desc | Ignored. rank asc returns the same order. |
scid | 4fc10100-5f7a-4470-899b-280835760c07 (Minecraft's Xbox service config id) | Ignored. Omitting it still works. |
select | images | Ignored. Images are returned even without it. |
top | 75 | Ignored. top: 3 still returns 13 items. |
skip | not sent | Ignored. |
The "blob" in the path is literal. The service returns a precomputed list and does not run the query. Sending the exact body above is still the safest choice in case the service starts honoring it.
Accept-Encoding: gzip is honored. Leave it out if your HTTP client cannot inflate.
Response envelope
{
"status": "OK",
"code": 200,
"data": {
"Count": 13,
"Items": [ { "...": "..." } ],
"ConfigurationName": "DEFAULT"
}
}| Field | Type | Notes |
|---|---|---|
status | string | OK on success. |
code | int | Mirrors the HTTP status. |
data.Count | int | Number of items. 13 on the capture date. |
data.Items | array | The servers, see . |
data.ConfigurationName | string | DEFAULT. |
Returned order on the capture date
The order is fixed by the service. The real client shows servers in this order within each group.
| # | Title | url | Group |
|---|---|---|---|
| 1 | The Hive | geo.hivebedrock.network | Featured |
| 2 | CubeCraft | mco.cubecraft.net | Featured |
| 3 | Dimension Clash | (empty) | Creator |
| 4 | Lifeboat | mco.lbsg.net | Featured |
| 5 | Treasure Hunt | (empty) | Creator |
| 6 | Mineville Zeqa | play.inpvp.net | Featured |
| 7 | MegaSMP | play.megasmp.gg | Featured |
| 8 | Enchanted | play.enchanted.gg | Featured |
| 9 | OneBlockOnline | (empty) | Creator |
| 10 | GALAXITE | play.galaxite.net | Featured |
| 11 | GenWars | (empty) | Creator |
| 12 | SoulSteel | (empty) | Creator |
| 13 | Mob Maze | (empty) | Creator |
7. Item schema reference
Every item is a PlayFab Economy catalog item. Full example (The Hive, localized maps and long arrays trimmed):
{
"Id": "36943f1b-5a71-494a-a812-44b5dc33e27a",
"Type": "catalogItem",
"AlternateIds": [],
"Title": { "NEUTRAL": "The Hive", "neutral": "The Hive" },
"Description": {
"NEUTRAL": "The Hive offers fun & polished minigames, with advanced social features.",
"neutral": "The Hive offers fun & polished minigames, with advanced social features.",
"en_US": "The Hive offers fun & polished minigames, with advanced social features.",
"tr_TR": "The Hive, eğlenceli ve özenle hazırlanmış mini oyunlar ile gelişmiş sosyal özellikler sunar.",
"de_DE": "The Hive bietet unterhaltsame und ausgefeilte Minispiele mit fortgeschrittenen sozialen Funktionen."
},
"ContentType": "3PP_V2.0",
"Platforms": ["android.googleplay", "ios.store", "uwp.store", "title.bedrockvanilla", "..."],
"Tags": [],
"CreationDate": "2020-04-02T00:00:00Z",
"LastModifiedDate": "2025-05-29T00:00:00Z",
"StartDate": "2026-04-01T00:00:00Z",
"Contents": [],
"Images": [
{
"Tag": "Thumbnail",
"Id": "7770057a-2228-4fae-a8e0-5b208e7332ed",
"Type": "Thumbnail",
"Url": "https://xforgeassets001.xboxlive.com/pf-namespace-b63a0803d3653643/7770057a-2228-4fae-a8e0-5b208e7332ed/266855ec-063f-4dc6-90f3-63b87ef4b7a3.jpg"
},
{
"Tag": "screenshot",
"Id": "d7759349-ff95-419b-a682-3ebc8f304ba4",
"Type": "Screenshot",
"Url": "https://xforgeassets002.xboxlive.com/pf-title-b63a0803d3653643-20ca2/d7759349-ff95-419b-a682-3ebc8f304ba4/hive_screenshot_1.jpg"
}
],
"ItemReferences": [],
"DisplayProperties": {
"creatorName": "The Hive",
"maxClientVersion": "9.9.99",
"minClientVersion": "1.6.0",
"news": "Modify gameplay, create and share presets and play Hive minigames the way you like it with Custom Servers!",
"newsTitle": "Major update: Custom Servers",
"originalCreatorId": "dd292649e51db5f9",
"port": 19132,
"requireXBL": "True",
"storePageId": "ServerPage_master_player_account!DD292649E51DB5F9",
"url": "geo.hivebedrock.network",
"whitelistUrl": "*.hivebedrock.network",
"allowListUrl": "*.hivebedrock.network",
"experienceId": "36943f1b-5a71-494a-a812-44b5dc33e27a",
"requiredExperiments": [],
"availableGames": [
{
"title": "SkyWars",
"subtitle": "Strike it lucky in SkyWars!",
"description": "Mine rare ores for powerful gear, bridge between islands, and dominate the skies. Last team standing wins!",
"imageTag": "AvailableGameSkyWars"
}
]
},
"IsStackable": false,
"CreatorEntityKey": { "Id": "DD292649E51DB5F9", "Type": "master_player_account", "TypeString": "master_player_account" },
"IsHydrated": false,
"Keywords": {}
}Top level fields
| Field | Type | Always present | Description |
|---|---|---|---|
Id | string (UUID) | yes | Catalog item id. Equal to DisplayProperties.experienceId for every item seen. Use it as the stable key. |
Type | string | yes | Always catalogItem. |
AlternateIds | array | yes | Always empty. |
Title | object | yes | Localized name map. In practice only NEUTRAL and neutral are present, so names are never translated. |
Description | object | yes | Localized description map. Keys are NEUTRAL, neutral and locale codes like en_US, tr_TR. Some servers ship 31 locales, others only the two neutral keys. |
ContentType | string | yes | Always 3PP_V2.0. |
Platforms | array of string | yes | Store platforms the item is published for. Observed values: android.amazonappstore, android.googleplay, b.store, ios.store, java, nx.store, nx2.store, oculus.store.gearvr, oculus.store.rift, ps5.store, title.bedrockvanilla, title.earth, title.sunderland, uwp.store, uwp.store.mobile, win10.onestore, xboxone.store. |
Tags | array | yes | Always empty. Not usable to tell featured and creator apart. |
CreationDate | ISO 8601 | yes | When the catalog entry was created. |
LastModifiedDate | ISO 8601 | yes | Last edit. |
StartDate | ISO 8601 | yes | Publication start. 0001-01-01T00:00:00Z means "no start date". |
Contents | array | yes | Always empty. |
Images | array | yes | See . |
ItemReferences | array | yes | Always empty. |
DisplayProperties | object | yes | The server specific data, see below. |
IsStackable | bool | yes | Always false. |
CreatorEntityKey | object | yes | PlayFab entity of the publisher. Id is the uppercase form of originalCreatorId. |
IsHydrated | bool | yes | Always false. |
Keywords | object | yes | Always empty. |
DisplayProperties
| Field | Type | Present on | Description |
|---|---|---|---|
creatorName | string | all | Publisher display name, shown under the server name. Several servers use Minecraft here. |
url | string | all | Server hostname. Empty string for creator experiences. |
port | int | all | Server port. 19132 everywhere, including creator experiences where it is meaningless. Note it is a JSON number while most other values are strings. |
experienceId | string (UUID) | all | Id passed to join/experience. Equal to the item Id. |
news | string | all | News body. May be an empty string. May contain \n. |
newsTitle | string | all | News headline. May be an empty string. |
availableGames | array | all | Game mode cards, see below. |
originalCreatorId | string | all | Lowercase hex id of the publisher's PlayFab master account. |
storePageId | string | all | Marketplace page id. Either ServerPage_master_player_account!{CREATOR} or ServerOffers_{uuid}, sometimes empty. |
minClientVersion | string | all | Minimum game version, 1.6.0 everywhere. |
maxClientVersion | string | all | Maximum game version, 9.9.99 everywhere. |
requireXBL | string | all | "True" as a string, not a bool. The server requires Xbox Live authentication. |
whitelistUrl | string | all | Host pattern the client allows for transfers, like *.hivebedrock.network. localhost on creator experiences. |
allowListUrl | string | all | Same value as whitelistUrl (newer name). |
rank | int | some | Present on most creator experiences and on Lifeboat. Values seen: 10, 15, 20, 25, 30, 35, 45. Does not match the returned order. Purpose unconfirmed. |
requiredExperiments | array | most | Always empty when present. Missing on Dimension Clash. |
availableGames[]
Each entry describes one game mode. The real client renders these as the showcase cards in the detail pane.
| Field | Type | Description |
|---|---|---|
title | string | Mode name, like SkyWars. |
subtitle | string | Short tagline. |
description | string | One or two sentences. |
imageTag | string | Matches the Tag of one entry in the item's Images array. That image is the card picture. |
Every imageTag seen on the capture date had a matching image. Counts per server ranged from 3 (OneBlockOnline) to 12 (GALAXITE).
8. Images
Image entry
| Field | Type | Description |
|---|---|---|
Type | string | One of Icon, Thumbnail, Banner, Screenshot, Activity. |
Tag | string | Free form label set by the publisher. Links images to availableGames[].imageTag. |
Id | string | Asset id. Sometimes a UUID, sometimes a path like b33d.../images/.../logomark_48x48 (1).png. |
Url | string | Absolute URL. May contain spaces, which must be percent encoded (%20) before requesting. |
Types in practice
| Type | Meaning | Notes |
|---|---|---|
Icon | Square logo for the list row. | Missing on GALAXITE, GenWars and Mob Maze. Fall back to Thumbnail. |
Thumbnail | Square or near square key art. | Present on most featured servers. |
Banner | Wide header art. | Some publishers file banners as Screenshot with Tag = Banner. |
Screenshot | Gallery picture. | Publishers also file icons (Tag = Icon), banners (Tag = Banner) and a generic placeholder (Tag = DevActivity) under this type. Filter those out by tag. |
Activity | Picture for an availableGames card. | Tag is usually activity_{hex} or AvailableGame{Name}. |
Per server counts on the capture date:
| Server | Icon | Thumbnail | Banner | Screenshot | Activity |
|---|---|---|---|---|---|
| The Hive | 1 | 1 | 1 | 15 | 0 |
| CubeCraft | 1 | 1 | 1 | 0 | 8 |
| Dimension Clash | 1 | 0 | 1 | 1 | 4 |
| Lifeboat | 1 | 1 | 1 | 9 | 0 |
| Treasure Hunt | 1 | 0 | 1 | 5 | 0 |
| Mineville Zeqa | 1 | 0 | 1 | 0 | 4 |
| MegaSMP | 1 | 1 | 1 | 4 | 0 |
| Enchanted | 1 | 1 | 1 | 9 | 2 |
| OneBlockOnline | 1 | 0 | 1 | 0 | 4 |
| GALAXITE | 0 | 1 | 0 | 26 | 0 |
| GenWars | 0 | 1 | 1 | 5 | 0 |
| SoulSteel | 1 | 0 | 0 | 6 | 0 |
| Mob Maze | 0 | 1 | 0 | 8 | 0 |
Downloading
- No authentication, plain
GET. xforgeassets*.xboxlive.comanswers withContent-Type: application/octet-streameven for JPEGs. Detect the format from the bytes (FF D8 FFfor JPEG,89 50 4E 47for PNG), not from the header.cdn.gatherings.franchise.minecraft-services.netanswers with proper types likeimage/png.- Formats seen: JPEG and PNG.
- Sizes vary a lot. A Hive thumbnail is about 180 KB. Screenshots are often full HD. Downscale before uploading to a texture atlas.
- The generic placeholder
https://cdn.gatherings.franchise.minecraft-services.net/public/discovery/generic/generic_activity.pngis used by several creator experiences. It is safe to skip.
Suggested selection logic
icon = first image with Type == "Icon"
else first image with Type == "Thumbnail"
showcase = images with Type == "Screenshot" and Tag not in {"Icon", "Banner", "DevActivity"}
if empty: images with Type == "Activity"
cards = for each availableGames entry, the image whose Tag == imageTag
9. Splitting featured and creator experiences
The game's HTML menu (gui/dist/hbui/index-*.js) reads two separate lists from its data model, featuredExperiences and creatorExperiences, and renders them as two sections:
hbui.PlayScreen.serverTab.featuredServer= "Featured experiences (%1$s)"hbui.PlayScreen.serverTab.creatorServer= "Creator experiences (%1$s)"
The split happens in native code, and the response itself has no explicit flag (Tags is empty). The rule that reproduces the game's grouping is:
| Condition | Group |
|---|---|
DisplayProperties.url is non empty | Featured experiences (dedicated partner servers you dial directly) |
DisplayProperties.url is empty | Creator experiences (servers hosted by Mojang's allocation service, only reachable through join/experience) |
Within each group keep the order the service returned.
10. Step 5: Joining a server
Featured servers
You can dial DisplayProperties.url and DisplayProperties.port directly over RakNet. That is what Kestrel does.
The service can also resolve them. For The Hive, join/experience returns a different host (geo.hivebedrock.cloud) than the catalog url (geo.hivebedrock.network). The real client probably goes through the join endpoint, which lets partners steer players without a catalog update.
Creator experiences
These have no address in the catalog. A server instance is allocated per join.
POST /api/v2.0/join/experience HTTP/1.1
Host: gatherings-secondary.franchise.minecraft-services.net
Content-Type: application/json
Accept: application/json
Authorization: MCToken eyJhbGciOiJS...
{ "experienceId": "622f462c-59fb-4f38-b195-6c26daf51e5c" }Response for a creator experience
{
"result": {
"networkProtocol": "Default",
"ipV4Address": "20.229.18.43",
"port": 31054,
"destinationInfo": {
"creatorId": "b33d057baa19fef4",
"experienceId": "622f462c-59fb-4f38-b195-6c26daf51e5c",
"experienceName": "Dimension Clash",
"scenarioId": "4785d233-caa0-410a-9745-910f3c9455d1",
"mpsasScenarioId": "4785d233-caa0-410a-9745-910f3c9455d1-public",
"serverId": "f7495_1-4785d233-caa0-410a-9745-910f3c9455d1-public|8b077455be6a4d1e8fb0ba42890fdf33",
"targetId": "9de50182-29d5-4e27-8d41-d3204942d2a9",
"worldId": "d100196c-f3b9-4351-bcbc-a41eca54fc34",
"worldName": "Dimension Clash - Menu"
}
}
}Response for a featured server
{
"result": {
"networkProtocol": "Default",
"ipV4Address": "geo.hivebedrock.cloud",
"port": 19132,
"destinationInfo": {
"creatorId": "dd292649e51db5f9",
"experienceId": "36943f1b-5a71-494a-a812-44b5dc33e27a",
"experienceName": "The Hive 3PP"
}
}
}| Field | Type | Description |
|---|---|---|
result.networkProtocol | string | Default, meaning plain RakNet. |
result.ipV4Address | string | Host to dial. Despite the name it can be a hostname. Resolve it with DNS. |
result.port | int | UDP port. Creator experiences get a random high port (30093 and 31054 on two consecutive joins). |
result.destinationInfo.experienceName | string | Display name. Featured partners get a 3PP suffix. |
result.destinationInfo.worldName | string | Creator experiences only. The world you land in, usually the lobby. |
result.destinationInfo.scenarioId, mpsasScenarioId, serverId, targetId, worldId | string | Creator experiences only. Allocation internals. Not needed to connect. |
Notes:
- Each call allocates. Call it right before dialing, not when the list loads, because the address changes between calls.
- An anonymous MCToken was accepted on the capture date. The servers themselves have
requireXBL="True", so the RakNet login must still carry an Xbox Live signed chain. In practice use the player's signed in MCToken here, and refuse to join when the player is signed out.
11. Errors
All *.minecraft-services.net endpoints return errors in the same envelope:
{
"namespace": "ServiceRuntime",
"code": "Unauthorized",
"message": "Unauthorized.",
"customData": {}
}| Situation | HTTP | code | message |
|---|---|---|---|
discovery/blob/client without Authorization | 401 | Unauthorized | Unauthorized. |
join/experience with an empty or unknown experienceId (the all zero UUID) | 400 | InputError | Parameter is empty. (Parameter 'ExperienceId'), customData.parameterName = ExperienceId |
Unknown path on an allocation host, like /api/v2.0/join/experience on client.allocation.multiplayer... | 404 | OperationError | NotFound. |
PlayFab uses its own envelope:
{ "code": 403, "status": "Forbidden", "error": "NotAuthorizedByTitle", "errorCode": 1191, "errorMessage": "Action not authorized by title" }12. Localization
Titleis effectively never localized, onlyNEUTRALandneutralexist.Descriptionis keyed by underscore locale codes (tr_TR,en_US,pt_BR), the same codes the game uses for its.langfiles. Look up the player's code and fall back toNEUTRAL.news,newsTitleandavailableGamesare single language strings, English in practice.- The
Accept-Languageheader and thesession/startlocale do not change the response.Accept-Language: tr-TRstill returns all 31 description locales. - Some
newsstrings arrive already mojibaked by the publisher (Treasure Hunt hadΓû╢Γû╖Γû╢ OUT NOW!). That is in the source data, not a decoding bug on your side. - Section headings live in
resource_packs/oreui/texts/{locale}.langunderhbui.keys, not in the vanilla pack. Useful keys:
| Key | en_US | tr_TR |
|---|---|---|
hbui.PlayScreen.serverTab.featuredServer | Featured experiences (%1$s) | Öne çıkan deneyimler (%1$s) |
hbui.PlayScreen.serverTab.creatorServer | Creator experiences (%1$s) | Oluşturucu tecrübeleri (%1$s) |
hbui.PlayScreen.serverTab.externalServer | Other Server (%1$s) | |
hbui.PlayScreen.serverTab.newsTitle | News | Haberler |
hbui.PlayScreen.serverTab.ServerDescription.title | Description | Açıklama |
hbui.PlayScreen.serverTab.ServerDescription.peoplePlaying | %1$s people playing | |
hbui.PlayScreen.serverTab.ServerDescription.lowPing | Low ping | |
thirdPartyWorld.loadingFeaturedServers (vanilla pack) | Fetching Servers... |
13. Caching and refresh
- The MCToken lives about 4 hours (
validUntilminusissuedAt). - The list response carries no
Cache-ControlorETag. The publishers'LastModifiedDatevalues are months old, so fetching once per launch is plenty. - Image URLs are content addressed (asset ids in the path), so caching downloaded images on disk by URL is safe.
- A fresh anonymous PlayFab player is created every time you log in with a new
DeviceId. Persist one device id if you want to avoid that.
14. Things that do not work
These were tried while reverse engineering and are listed so nobody repeats them:
| Attempt | Result |
|---|---|
PlayFab Catalog/Search with filter: "contentType eq '3PP_V2.0'" and an entity token | Count: 0. The PlayFab side catalog search only exposes a handful of OfferCollectionQueries_V3.0 items to players. |
PlayFab Catalog/SearchItems (Economy v2) with ContentType eq '3PP_V2.0' | 0 items. |
PlayFab Client/LoginWithCustomID | 403, disabled for the title. |
join/experience on the frontend or mpsas allocation hosts | 404. It only exists on gatherings. |
Using Tags to split featured and creator | Tags is always empty. |
Paging with top and skip | Ignored, the full blob is always returned. |
Strings in libminecraftpe.so that confirm the pieces above: 3PP_V2.0, /api/v2.0/join/experience, /api/v2.0/join, /api/v2.0/join/eligibility, /api/v2.0/join/scenario, /api/v2.0/join/world/client, ThirdPartyServerRepository, CatalogBackendGatheringsTaskGroup, experienceId, availableGames.
15. Complete Python example
Standard library only. Prints both groups and resolves one creator experience.
import gzip
import json
import urllib.request
import uuid
GAME_VERSION = "1.26.51"
SCID = "4fc10100-5f7a-4470-899b-280835760c07"
FILTER = ("(contentType eq '3PP_V2.0') and platforms/any(tp: tp eq 'android.googleplay') "
"and platforms/any(tp: tp eq 'title.bedrockvanilla')")
def call(url, body=None, headers=None):
data = json.dumps(body).encode() if body is not None else None
request = urllib.request.Request(url, data, {
"Content-Type": "application/json",
"Accept": "application/json",
**(headers or {}),
})
with urllib.request.urlopen(request, timeout=30) as response:
raw = response.read()
if raw[:2] == b"\x1f\x8b":
raw = gzip.decompress(raw)
return json.loads(raw)
# 1. discovery
services = call(f"https://client.discovery.minecraft-services.net/api/v1.0/discovery/MinecraftPE/builds/{GAME_VERSION}")
env = services["result"]["serviceEnvironments"]
auth_uri = env["auth"]["prod"]["serviceUri"]
title_id = env["auth"]["prod"]["playfabTitleId"]
gatherings_uri = env["gatherings"]["prod"]["serviceUri"]
# 2. anonymous PlayFab login
login = call(f"https://{title_id.lower()}.playfabapi.com/Client/LoginWithIOSDeviceID", {
"CreateAccount": True,
"TitleId": title_id,
"DeviceId": str(uuid.uuid4()),
"OS": "iOS",
})
ticket = login["data"]["SessionTicket"]
# 3. MCToken
session = call(f"{auth_uri}/api/v1.0/session/start", {
"user": {"language": "en", "languageCode": "en-US", "regionCode": "US",
"token": ticket, "tokenType": "PlayFab"},
"device": {"applicationType": "MinecraftPE", "memory": "8589934592", "id": str(uuid.uuid4()),
"gameVersion": GAME_VERSION, "platform": "Windows10", "playFabTitleId": title_id,
"storePlatform": "uwp.store", "type": "Windows10"},
})
authorization = {"Authorization": session["result"]["authorizationHeader"]}
# 4. server list
blob = call(f"{gatherings_uri}/api/v2.0/discovery/blob/client", {
"count": True, "filter": FILTER, "orderBy": "startDate desc",
"scid": SCID, "select": "images", "top": 75,
}, authorization)
featured, creator = [], []
for item in blob["data"]["Items"]:
props = item["DisplayProperties"]
(featured if props.get("url") else creator).append(item)
print("Featured experiences")
for item in featured:
props = item["DisplayProperties"]
print(f" {item['Title']['NEUTRAL']:<16} {props['url']}:{props['port']} by {props['creatorName']}")
print("Creator experiences")
for item in creator:
props = item["DisplayProperties"]
print(f" {item['Title']['NEUTRAL']:<16} experience {props['experienceId']} by {props['creatorName']}")
# 5. resolve a creator experience
if creator:
joined = call(f"{gatherings_uri}/api/v2.0/join/experience",
{"experienceId": creator[0]["DisplayProperties"]["experienceId"]}, authorization)
result = joined["result"]
print(f"Join {result['destinationInfo']['experienceName']} at {result['ipV4Address']}:{result['port']}")16. Glossary
| Term | Meaning |
|---|---|
| 3PP | Third party partner. Mojang's name for partnered servers. |
3PP_V2.0 | PlayFab content type of partner server catalog items. |
| MCToken | Bearer token for *.minecraft-services.net, obtained from session/start. Sent as Authorization: MCToken .... |
| Gatherings | Mojang service for events and experiences. Hosts the server list and the join endpoint. |
| Experience | Anything joinable through gatherings. Every listed server has an experienceId. |
| Creator experience | Partner content without a dedicated address, hosted on Mojang's allocation service (MPSAS). |
| MPSAS | Multiplayer server allocation service. Appears as mpsasScenarioId in join responses. |
| SCID | Xbox Live service configuration id. 4fc10100-5f7a-4470-899b-280835760c07 is Minecraft's. |
Title 20CA2 | Minecraft Bedrock's PlayFab title. 6955F is Education Edition. |
| oreui / hbui | The game's HTML based menus and their text keys. |
Credits
The discovery endpoint and request body were first found in Daniel-Ric/PlayFab-Catalog-Service-Bedrock. The join flow, field behavior, error cases and grouping rule were verified independently for this document.