Edition Detection
The Minecraft Heads API supports both Java Edition and Bedrock Edition players. Since the two editions use completely different account systems and skin APIs, the API must determine which edition a player belongs to before it can fetch their skin. This is done automatically based on the format of the input string.
Detection Rules
The API applies three rules, evaluated in order:
| Rule | Condition | Detected Edition |
|---|---|---|
| 1 | Input starts with 0000 |
Bedrock (XUID) |
| 2 | Input starts with . (a dot) |
Bedrock (gamertag) |
| 3 | Anything else | Java (username or UUID) |
There is no ambiguity between the formats. Java usernames cannot start with 0000 (Mojang usernames are 3-16 alphanumeric characters and underscores), and they cannot start with a dot. Java UUIDs are hexadecimal and will not start with 0000 in practice because the UUID version nibble occupies a different position.
The detection function is defined in utils/minecraft.js:
function isBedrock(input) {
return input.startsWith('0000') || input.startsWith('.');
}
Java Edition
Username Resolution
When the input does not match any Bedrock pattern, it is treated as a Java Edition identifier. If the input is not a UUID, the API resolves it to a UUID through the Mojang API:
GET https://api.mojang.com/users/profiles/minecraft/{username}
This returns the player's UUID, which is then used to fetch their full profile.
UUID Formats
The API accepts Java UUIDs in two formats:
- With dashes:
069a79f4-44e9-4726-a5be-fca90e38aaf5 - Without dashes:
069a79f444e94726a5befca90e38aaf5
Both are recognized as UUIDs using a regex check:
function isUUID(input) {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const shortUuidRegex = /^[0-9a-f]{32}$/i;
return uuidRegex.test(input) || shortUuidRegex.test(input);
}
If the input is already a UUID, the username-to-UUID resolution step is skipped, and the API goes directly to the session server.
Profile and Skin Fetching
The player's skin texture URL is retrieved from the Mojang session server:
GET https://sessionserver.mojang.com/session/minecraft/profile/{uuid}
The response contains a base64-encoded textures property that, when decoded, includes the skin URL:
{
"textures": {
"SKIN": {
"url": "https://textures.minecraft.net/texture/..."
}
}
}
The API decodes this and extracts the skin URL for rendering.
Examples
# By username
curl -o head.png https://api.mcheads.org/head/Notch/128
# By UUID (no dashes)
curl -o head.png https://api.mcheads.org/head/069a79f444e94726a5befca90e38aaf5/128
# By UUID (with dashes)
curl -o head.png https://api.mcheads.org/head/069a79f4-44e9-4726-a5be-fca90e38aaf5/128
All three produce the same result.
Bedrock Edition
Bedrock Edition players use Xbox Live accounts. Their skins are managed separately from Java Edition and are accessed through the GeyserMC API.
XUID Lookup
An XUID (Xbox User ID) is a numeric identifier assigned to every Xbox Live account. When the input starts with 0000, the API treats it as a Bedrock XUID and fetches the skin directly:
GET https://api.geysermc.org/v2/skin/{xuid}
Example:
curl -o head.png https://api.mcheads.org/head/0000123456789/128
The 0000 prefix is a convention used by this API to signal that the input is a Bedrock XUID rather than a Java identifier. Real XUIDs are long numeric strings; the prefix ensures they are not mistaken for Java UUIDs or usernames.
Gamertag Lookup
When the input starts with a dot (.), the API strips the dot and treats the remainder as an Xbox Live gamertag. It first resolves the gamertag to an XUID:
GET https://api.geysermc.org/v2/xbox/xuid/{gamertag}
Then fetches the skin using that XUID:
GET https://api.geysermc.org/v2/skin/{xuid}
Example:
curl -o head.png https://api.mcheads.org/head/.ExampleGamertag/128
The dot prefix is required. Without it, ExampleGamertag would be interpreted as a Java username and sent to the Mojang API, which would fail if no Java player exists with that name.
Gamertags with Spaces
Xbox Live gamertags can contain spaces. When using the API via a URL, encode spaces as %20:
curl -o head.png "https://api.mcheads.org/head/.Example%20Gamertag/128"
Steve Fallback
If a Bedrock player has no skin data available (the GeyserMC API returns an empty response), or if the GeyserMC API request fails entirely, the API falls back to the default Steve skin. This is done by requesting the Java profile for the username "Steve":
if (Object.keys(skinResponse.data).length === 0) {
return getJavaProfile('Steve');
}
This ensures that Bedrock requests always return a valid image rather than an error. The Steve fallback applies in two cases:
Empty skin data -- The GeyserMC API responds successfully but the skin data object is empty. This can happen for players who have never changed their default skin.
API error -- The GeyserMC API is unreachable or returns an error. The
catchblock also falls back to Steve:
async function getBedrockProfile(input) {
try {
// ... resolve XUID and fetch skin ...
} catch (error) {
return getJavaProfile('Steve');
}
}
This means Bedrock requests are more resilient than Java requests. A Java request for a nonexistent player will return a 500 error, but a Bedrock request for any input will always return an image.
How the API Uses Edition Internally
Once the edition is detected, it is passed through the render pipeline for two purposes:
1. Profile Resolution
The getProfile function routes to the correct upstream API:
async function getProfile(input) {
const edition = isBedrock(input) ? 'bedrock' : 'java';
if (edition === 'bedrock') {
return { profile: await getBedrockProfile(input), edition };
} else {
return { profile: await getJavaProfile(input), edition };
}
}
2. Usage Statistics
Every render records which edition was used, so the stats endpoints can report Java and Bedrock usage separately:
recordStats('head', input, edition);
The stats are accessible at /allstats (Java), /allstatsbedrock (Bedrock), and /allstatsSorted (both, sorted by count).
Decision Flowchart
Here is the complete decision path for any input:
Input received
|
+-- Starts with "0000"?
| YES --> Bedrock XUID
| Fetch skin from GeyserMC: /v2/skin/{xuid}
| Empty response? --> Fall back to Steve
|
+-- Starts with "."?
| YES --> Bedrock gamertag
| Strip the dot
| Resolve XUID from GeyserMC: /v2/xbox/xuid/{gamertag}
| Fetch skin from GeyserMC: /v2/skin/{xuid}
| Empty response or error? --> Fall back to Steve
|
+-- Otherwise
Java Edition
Is it a UUID? (32 hex chars or UUID with dashes)
YES --> Fetch profile from Mojang session server
NO --> Resolve username to UUID via Mojang API
Fetch profile from Mojang session server
Decode base64 textures property
Extract skin URL
Edge Cases
MHF Heads
MHF preset names (like MHF_Creeper, MHF_Skeleton) do not start with 0000 or ., so they are routed through the Java path. These are valid Mojang usernames with pre-assigned UUIDs that resolve to well-known mob and item textures.
Case Sensitivity
Java usernames are case-insensitive at the Mojang API level. Notch, notch, and NOTCH all resolve to the same player. Xbox Live gamertags are also case-insensitive.
Numeric Usernames
A Java username that happens to be entirely numeric (e.g., 12345) does not start with 0000 or ., so it is correctly routed to the Java path. Only inputs beginning with exactly 0000 are treated as Bedrock XUIDs.