Dokumentation
Für Entwickler
Wie der Import-String aufgebaut ist, damit ein AddOn oder ein Tool ihn lesen und schreiben kann.
Aufbau
Ein Import-String besteht aus einem Präfix und einem Payload:
KeystoneLoot:v3,<base64( zlib( JSON ) )>Der Payload entsteht in drei Schritten: das JSON wird serialisiert, mit zlib (RFC 1950, also deflate mit Header) komprimiert und anschließend Base64-kodiert, und zwar Standard-Base64 mit + und /, nicht die URL-sichere Variante. Zum Lesen dieselben Schritte rückwärts.
Payload
Das JSON ist ein Objekt, dessen Schlüssel Blizzards numerische Spezialisierungs-IDs sind. Ein String kann mehrere Spezialisierungen enthalten.
{
"62": [
{ "tier": 3, "itemId": 271874, "gems": [240967], "enchant": 7991 },
{ "tier": 3, "itemId": 239648, "bonusIds": [13751, 13836, 9627] },
{ "tier": 5, "itemId": 251232 },
{ "tier": 3, "itemId": 271564 }
]
}Felder
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
itemId | integer | ja | Die Item-ID. |
tier | integer | nein | Priorität, 1 bis 5. Default ist 2. |
bonusIds | integer[] | nein | Bonus-IDs des Items. |
gems | integer[] | nein | Item-IDs der eingesetzten Edelsteine. |
enchant | integer | nein | ID der Verzauberung. |
Tier-Werte
Die Prioritätsstufen, die tier annehmen kann. So heißen sie auch im AddOn:
| Wert | Symbol | Bedeutung |
|---|---|---|
| 1 | Wäre schön | |
| 2 | Muss haben (Default) | |
| 3 | Best in Slot | |
| 4 | Transmog | |
| 5 | Katalysator |
Vier Dinge, die eine Tabelle nicht sagen kann:
- Diese Seite schreibt Rang 3 und Rang 5. 3 ist, was du tragen sollst; 5 ist ein Item, das in den Katalysator gehört, und zu jedem solchen Eintrag steht das Ergebnis als eigener Rang-3-Eintrag daneben.
bonusIdsträgt hier, was das Item beschreibt: die Kette bei gefertigten Teilen, das Mythisch+-Label bei Drops aus einem Schlüsselstein. Das Itemlevel steht nicht darin, weil dein Exemplar den Rang trägt, auf dem du es gelootet hast, und nicht den, auf den die Liste zielt.enchantist eine SpellItemEnchantmentID, nicht die Item-ID der Verzauberung. Das ist der Wert, den Wowheads ench-Parameter und das AddOn brauchen.
Leere Arrays und null werden beim Schreiben weggelassen, statt sie mitzuschicken.
Beispiele
Drei Implementierungen, je nachdem wo dein Code läuft. Alle drei erzeugen denselben String.
JavaScript
Beide Richtungen im Browser, ohne Dependencies. CompressionStream kann jeder aktuelle Browser, deshalb reicht zlib ohne Library:
// A KeystoneLoot:v3 string, built in the browser.
async function encodeV3(table) {
const bytes = new TextEncoder().encode(JSON.stringify(table));
// zlib, RFC 1950 (header 0x78 0x9c) — not "deflate-raw".
const stream = new CompressionStream("deflate");
const writer = stream.writable.getWriter();
writer.write(bytes);
writer.close();
const packed = new Uint8Array(
await new Response(stream.readable).arrayBuffer()
);
// Standard base64, with + and /, not base64url.
let binary = "";
for (const byte of packed) binary += String.fromCharCode(byte);
return "KeystoneLoot:v3," + btoa(binary);
}
const string = await encodeV3({
62: [{ tier: 3, itemId: 271874, gems: [240967], enchant: 7991 }],
});Und zurück:
// And back again.
async function decodeV3(string) {
const payload = string.slice(string.indexOf(",") + 1);
const bytes = Uint8Array.from(atob(payload), (c) => c.charCodeAt(0));
const stream = new DecompressionStream("deflate");
const writer = stream.writable.getWriter();
writer.write(bytes);
writer.close();
return JSON.parse(await new Response(stream.readable).text());
}TypeScript
Dasselbe mit Typen, samt Struktur des Payloads. Wieder die Browser-Variante, also async:
export type Tier = 1 | 2 | 3 | 4 | 5;
export interface Entry {
itemId: number;
tier?: Tier;
bonusIds?: number[];
gems?: number[];
enchant?: number;
}
/** Keyed by Blizzard's numeric specialisation id. */
export type ImportTable = Record<number, Entry[]>;
const PREFIX = "KeystoneLoot:v3,";
export async function encodeV3(table: ImportTable): Promise<string> {
const bytes = new TextEncoder().encode(JSON.stringify(table));
const stream = new CompressionStream("deflate");
const writer = stream.writable.getWriter();
writer.write(bytes);
writer.close();
const packed = new Uint8Array(
await new Response(stream.readable).arrayBuffer()
);
let binary = "";
for (const byte of packed) binary += String.fromCharCode(byte);
return PREFIX + btoa(binary);
}
export async function decodeV3(text: string): Promise<ImportTable> {
const payload = text.slice(text.indexOf(",") + 1);
const bytes = Uint8Array.from(atob(payload), (c) => c.charCodeAt(0));
const stream = new DecompressionStream("deflate");
const writer = stream.writable.getWriter();
writer.write(bytes);
writer.close();
return JSON.parse(await new Response(stream.readable).text()) as ImportTable;
}Node.js
Serverseitig als ES-Modul. node:zlib macht dasselbe wie CompressionStream, Buffer das Base64, und der String ist Byte für Byte derselbe wie im Browser:
import { deflateSync, inflateSync } from "node:zlib";
const PREFIX = "KeystoneLoot:v3,";
export function encodeV3(table) {
const json = Buffer.from(JSON.stringify(table), "utf8");
return PREFIX + deflateSync(json).toString("base64");
}
export function decodeV3(text) {
const payload = text.slice(text.indexOf(",") + 1);
const json = inflateSync(Buffer.from(payload, "base64"));
return JSON.parse(json.toString("utf8"));
}API
Wer den String nicht selbst bauen will, holt ihn fertig ab:
Antwort ist JSON, hier für /api/import/deathknight/unholy:
{
"class": "deathknight",
"spec": "unholy",
"classId": 6,
"specId": 252,
"view": "overall",
"build": null,
"updated": "2026-08-12T19:31:27Z",
"string": "KeystoneLoot:v3,eJydkDFuwzAMRe..."
}Ohne Parameter bekommst du die Overall-Liste. Zwei optionale gibt es:
view:overall,mythicplusoderraidbuild: der Heldentalent-Build, wenn ein Spec mehrere Listen hat.
$ curl "https://keystoneloot.io/api/import/deathknight/unholy?view=raid"
$ curl "https://keystoneloot.io/api/import/priest/discipline?build=Oracle"
updated ist der Timestamp des letzten Updates. Wer ihn speichert, kann beim nächsten Mal vergleichen und den Import überspringen, wenn sich nichts geändert hat.
Für die Shell gibt es ?format=plain. Dann kommt nur der String selbst, mit einem Zeilenumbruch am Ende. Die Felder von oben stehen dort in den Headern: x-keystoneloot-class-id, -spec-id, -view, -build und -updated.
$ curl "https://keystoneloot.io/api/import/mage/fire?format=plain"
KeystoneLoot:v3,eJydkTtuwzAMhu/CmYNIiqSo...Fehler kommen in derselben Form wie Treffer, damit du für eine Route nicht zwei Parser brauchst. Die gültigen Werte stehen als Array dabei:
{
"error": "Unknown build: Quatsch",
"available": ["Oracle", "Voidweaver"],
"docs": "https://keystoneloot.io/en/developers#api"
}Damit niemand 40 URLs raten muss, listet ein Index alle Specs auf, mit den verfügbaren Views und Builds:
Die Listen ändern sich höchstens einmal pro Woche. Bitte cache die Antworten, die Header sagen dir wie lange.
Ältere Formate
v1 und v2 sind unkomprimierter Klartext und werden nicht mehr unterstützt. Bitte ausschließlich v3 erzeugen.
Ausprobieren
Der Import-String-Editor zerlegt einen String im Browser und zeigt, was darin steht. Praktisch zum Gegenprüfen einer eigenen Implementierung.