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.net
  • xforgeassets001.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.
  • MinecraftPE is the client type. The build number is the game version as major.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.com answers with Content-Type: application/octet-stream even for JPEGs. Detect the format from the bytes (FF D8 FF for JPEG, 89 50 4E 47 for PNG), not from the header.
  • cdn.gatherings.franchise.minecraft-services.net answers with proper types like image/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.png is 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

  • Title is effectively never localized, only NEUTRAL and neutral exist.
  • Description is keyed by underscore locale codes (tr_TR, en_US, pt_BR), the same codes the game uses for its .lang files. Look up the player's code and fall back to NEUTRAL.
  • news, newsTitle and availableGames are single language strings, English in practice.
  • The Accept-Language header and the session/start locale do not change the response. Accept-Language: tr-TR still returns all 31 description locales.
  • Some news strings 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}.lang under hbui. 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 (validUntil minus issuedAt).
  • The list response carries no Cache-Control or ETag. The publishers' LastModifiedDate values 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.

添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论