/api/v1Entdeckungsdokument
Gibt Scopes, Quoten, Webhook-Ereignisse und das vollständige Katalog der Operationen zurück. Jeder gültige API-Schlüssel funktioniert.
Die Verkäufer Open API ist für Verkäufer, die Discord-Benachrichtigungen, Bestands-Synchronisation, Zapier-ähnliche Automatisierung oder ein benutzerdefiniertes Backoffice auf RMT.GG wünschen.
Erstelle einen API-Schlüssel in den Entwicklereinstellungen und rufe dann Discovery auf, um den Live-Katalog anzuzeigen.
/api/v1Gibt Scopes, Quoten, Webhook-Ereignisse und das vollständige Katalog der Operationen zurück. Jeder gültige API-Schlüssel funktioniert.
Sende deinen Live-Geheimschlüssel bei jeder /api/v1-Anfrage. Bevorzuge nur HTTPS. Betten Sie niemals Schlüssel in öffentlichen Clients oder Browser-Bundles ein.
Bevorzugter Header
Authorization: Bearer rmt_sk_live_<prefix>_<secret>Alternativer Header
X-Api-Key: rmt_sk_live_<prefix>_<secret>Wiederverwendbarer TypeScript-Client (Bearer-Auth, typisierte Fehler, 429 Wiederholung)
const API_BASE = "https://rmt.gg/api/v1";
const API_KEY = process.env.RMT_API_KEY!; // rmt_sk_live_…
export class RmtApiError extends Error {
constructor(
readonly status: number,
readonly code: string | undefined,
message: string,
readonly retryAfterSec?: number,
) {
super(message);
this.name = "RmtApiError";
}
}
type RmtFetchInit = RequestInit & { idempotencyKey?: string };
export async function rmtFetch<T>(path: string, init: RmtFetchInit = {}): Promise<T> {
const headers = new Headers(init.headers);
headers.set("Authorization", `Bearer ${API_KEY}`);
// Alternate: headers.set("X-Api-Key", API_KEY);
headers.set("Accept", "application/json");
if (init.body && !headers.has("Content-Type")) {
headers.set("Content-Type", "application/json");
}
if (init.idempotencyKey) headers.set("Idempotency-Key", init.idempotencyKey);
const res = await fetch(`${API_BASE}${path}`, { ...init, headers });
const retryAfter = Number(res.headers.get("Retry-After") ?? "");
const body = (await res.json().catch(() => ({}))) as {
error?: string;
code?: string;
retryAfter?: number;
};
if (res.status === 429) {
throw new RmtApiError(
429,
body.code ?? "RATE_LIMITED",
body.error ?? "Rate limited",
Number.isFinite(retryAfter) ? retryAfter : body.retryAfter,
);
}
if (!res.ok) {
throw new RmtApiError(res.status, body.code, body.error ?? res.statusText);
}
return body as T;
}
export async function withRetry<T>(fn: () => Promise<T>, maxAttempts = 4): Promise<T> {
let attempt = 0;
for (;;) {
try {
return await fn();
} catch (err) {
attempt += 1;
if (!(err instanceof RmtApiError) || err.status !== 429 || attempt >= maxAttempts) {
throw err;
}
const waitSec = Math.max(1, err.retryAfterSec ?? 1);
await new Promise((r) => setTimeout(r, waitSec * 1000));
}
}
}
Rotieren bei Leck
Wenn ein Schlüssel geleakt wird, widerrufe ihn in den Entwicklereinstellungen und erstelle einen neuen. Aktualisiere deine Automatisierung, bevor du widerrufst, wenn du live bist.
Jeder API-Schlüssel trägt Scopes, die Endpunkte steuern. Fehlender Scope gibt 403 SCOPE_MISSING zurück.
offers:read: Liste und hole deine Angebote.offers:write: Erstelle, aktualisiere, veröffentliche und lösche Angebote.orders:read: Liste und hole Verkäuferbestellungen.orders:write: Markiere Bestellungen als geliefert.webhooks:manage: Reserviert für zukünftige Open API Webhook-Verwaltung. Konfiguriere Discord/Telegram in den Benachrichtigungen und JSON-Webhooks in den Entwicklereinstellungen heute.checkout:write: Erstellen und Lesen von gehosteten Checkout-Sessions. Erfordert die Genehmigung des Administrators für Partner-Checkout.Standard-Schlüssel-Scope
Neue Schlüssel erhalten offers:read, offers:write, orders:read und orders:write. Ausgehende Webhook CRUD bleibt in der UI der Einstellungen (Sitzungsauthentifizierung).
Angebotsidentifikatoren akzeptieren den öffentlichen URL-Slug oder die numerische ID. Antworten lassen interne ID und sellerId weg.
Was PATCH noch nicht ändern kann
Bestandszeilen, Optionspreise, Medien und Attribute werden im Verkäufer-Editor (oder zukünftigen Endpunkten) verwaltet, nicht über PATCH heute.
/api/v1/offersFiltern mit archive=active (Standard), archiviert oder alle.
Anfrage
archive| Name | In | Typ | Erforderlich | Beschreibung |
|---|---|---|---|---|
archive | query | string | Optional | One of "active" (default), "archived", or "all". |
/api/v1/offersErstellt einen leeren Entwurf, der dem authentifizierten Verkäufer gehört. Kein Körper erforderlich.
/api/v1/offers/:urlOrIdLade nach öffentlichem URL-Slug oder numerischer ID. Beziehungen (Optionen) können enthalten sein; Bestandsartikel sind nicht enthalten.
Anfrage
urlOrId| Name | In | Typ | Erforderlich | Beschreibung |
|---|---|---|---|---|
urlOrId | path | string | erforderlich | Offer.url slug or Offer.id. |
/api/v1/offers/:urlOrIdPatch ein sicheres Teilset von Angebotsfeldern. Gibt offer.updated aus, wenn ausgehende Webhooks konfiguriert sind.
Anfrage
urlOrIdtitledescriptionvisibilitycategoryIdofferingIdthumbnailofferTypelistingMode| Name | In | Typ | Erforderlich | Beschreibung |
|---|---|---|---|---|
urlOrId | path | string | erforderlich | Offer.url slug or Offer.id. |
title | body | string | Optional | Listing title. |
description | body | string | Optional | Listing description. |
visibility | body | string | Optional | PUBLIC | PRIVATE | UNPUBLISHED. |
categoryId | body | number | Optional | Catalog category id. |
offeringId | body | number | Optional | Catalog offering id. |
thumbnail | body | string | Optional | Thumbnail URL or asset reference. |
offerType | body | string | Optional | Offer type string used by the listing. |
listingMode | body | string | Optional | Listing mode (for example STANDARD, RANK_BOOST, SESSION). |
/api/v1/offers/:urlOrIdDie gleichen Lösch-/Archivierungsregeln wie die Verkäufer-UI.
Anfrage
urlOrId| Name | In | Typ | Erforderlich | Beschreibung |
|---|---|---|---|---|
urlOrId | path | string | erforderlich | Offer.url slug or Offer.id. |
/api/v1/offers/:urlOrId/publishVeröffentlicht einen Entwurf (oder ändert die Sichtbarkeit). Schlägt mit 400 fehl, wenn erforderliche Angebotsfelder unvollständig sind.
Anfrage
urlOrIdvisibility| Name | In | Typ | Erforderlich | Beschreibung |
|---|---|---|---|---|
urlOrId | path | string | erforderlich | Offer.url slug or Offer.id. |
visibility | body | string | Optional | Optional. PUBLIC (default), PRIVATE, or UNPUBLISHED. |
Siehe kaufbare Mengen pro Stufe, ordne die Lieferfeldnamen dem richtigen Angebot zu und fülle dann die Menge oder die gespeicherten Schlüssel und Konten nach.
So funktioniert das Matching
GET /api/v1/stock?fields=username,password findet Angebote, deren Schema diese Felder hat. Nachfüllen mit Optionsnamen (oder optionId) und Feldnamen. Interne Feld-IDs sind nicht erforderlich. Antworten enthalten niemals Anmeldeinformationen.
/api/v1/stockGibt die Mengen pro Stufe und die Lieferfeldnamen zurück, damit Sie Schlüssel und Konten dem richtigen Angebot zuordnen können. Filtern Sie mit q, fields, stockMode und lowStock. Gibt niemals Anmeldeinformationen zurück.
Anfrage
qfieldsstockModelowStockarchive| Name | In | Typ | Erforderlich | Limits | Beschreibung |
|---|---|---|---|---|---|
q | query | string | Optional | Max 80 | Filter by listing title or url slug. |
fields | query | string | Optional | Comma-separated delivery field names. The listing must have all of them (Username,Password). Names match case-insensitively. | |
stockMode | query | string | Optional | QUANTITY or COMPLEX. Listing must have at least one option in that mode. | |
lowStock | query | number | Optional | Keep listings that have a finite tier with available less than or equal to this number. | |
archive | query | string | Optional | One of "active" (default), "archived", or "all". |
/api/v1/offers/:urlOrId/stockGleiche StockOffer-Form wie der Index, für eine URL oder numerische ID. Nur Mengen.
Anfrage
urlOrId| Name | In | Typ | Erforderlich | Beschreibung |
|---|---|---|---|---|
urlOrId | path | string | erforderlich | Offer.url slug or Offer.id. |
/api/v1/offers/:urlOrId/stockMengenstufen: hinzufügen, entfernen oder festlegen. Gespeicherte Artikelstufen: Artikelobjekte nach Feldnamen, keys[], wenn es ein Feld gibt, oder durch Text getrennt. Mehrere Stufen in einem Aufruf über options[]. dryRun zeigt eine Vorschau des Matchings. onDuplicate standardmäßig auf überspringen.
{
"option": "1 Month",
"add": 50
}Anfrage
urlOrIdoptionoptionIdaddremovesetitemskeystextdelimiterheadersoptionsdryRunonDuplicate| Name | In | Typ | Erforderlich | Limits | Beschreibung |
|---|---|---|---|---|---|
urlOrId | path | string | erforderlich | Offer.url slug or Offer.id. | |
option | body | string | Bedingt | Pricing option name (case-insensitive). Omit when the listing has a single tier. | |
optionId | body | number | Bedingt | Pricing option id from GET stock. Wins over option when both are sent. Ambiguous names return 409 OPTION_AMBIGUOUS. | |
add | body | number | Bedingt | 1-1,000,000 | QUANTITY: add this many units. Fails with 400 UNLIMITED_STOCK if the tier is unlimited. |
remove | body | number | Bedingt | 1-1,000,000 | QUANTITY: withdraw this many units. Fails with 400 INSUFFICIENT_STOCK when there is not enough. |
set | body | number | null | Bedingt | QUANTITY: set an absolute count. null means unlimited. Cannot go below units held in checkout. | |
items | body | object[] | Bedingt | Max 1,000 | COMPLEX: objects keyed by delivery field name, for example { "Username": "a", "Password": "b" }. Names match case-insensitively. |
keys | body | string[] | Bedingt | Max 1,000 | COMPLEX: license keys when the listing has exactly one delivery field. Otherwise 400 FIELD_MAPPING_AMBIGUOUS. |
text | body | string | Bedingt | Max 1,000 rows | COMPLEX: delimited paste. A header row that matches field names is detected automatically. Otherwise columns map in field sort order when the column count matches. |
delimiter | body | string | Optional | Default : | Delimiter for text. Ignored unless text is sent. |
headers | body | string[] | Optional | Optional column headers for text when the first line is data, not names. | |
options | body | object[] | Bedingt | Restock several tiers in one call. Each element is the same shape as a single-option body (option, add, items, …). | |
dryRun | body | boolean | Optional | Preview matching and counts without writing. Default false. | |
onDuplicate | body | string | Optional | skip (default) or error | COMPLEX: skip existing unsold fingerprints, or fail the request with 409 DUPLICATE_ITEMS. |
Verwende POST /api/v1/offers/:url/stock mit items[] für Konten oder keys[] für Lizenzcodes mit einem Feld. In 1.000 Zeilen pro Anfrage aufteilen.
Wähle die Nutzlast, die zur Anzeige passt
Rufe zuerst GET stock auf. Wenn fields[] mehr als einen Namen hat, sende items-Objekte, die nach diesen Namen (Benutzername, Passwort, E-Mail) benannt sind. Wenn es genau ein Feld gibt, reicht keys[]. Mengenangaben verwenden add, nicht items.
1.000 Zeilen pro Anfrage. 5.000 unverkaufte Gegenstände pro Stufe. 300 Anfragen pro Minute. Duplikate werden standardmäßig übersprungen.
accounts.json (ein Objekt pro Konto)
[
{ "Username": "player1", "Password": "secret1", "E-Mail": "[email protected]" },
{ "Username": "player2", "Password": "secret2", "E-Mail": "[email protected]" }
]accounts.csv
Username,Password,E-Mail
player1,secret1,p1@example.com
player2,secret2,p2@example.comkeys.txt (ein Lizenzschlüssel pro Zeile)
AAAA-BBBB-CCCC
DDDD-EEEE-FFFF
GGGG-HHHH-IIIIcURL
# Inspect field names and stockMode
curl -s -H "Authorization: Bearer rmt_sk_live_…" \
-H "Accept: application/json" \
https://rmt.gg/api/v1/offers/YOUR_OFFER_URL/stock
# Preview (no write)
curl -s -X POST \
-H "Authorization: Bearer rmt_sk_live_…" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
https://rmt.gg/api/v1/offers/YOUR_OFFER_URL/stock \
-d '{"dryRun":true,"onDuplicate":"skip","option":"Premium","items":[{"Username":"player1","Password":"secret1","E-Mail":"[email protected]"}]}'
# Apply accounts
curl -s -X POST \
-H "Authorization: Bearer rmt_sk_live_…" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
https://rmt.gg/api/v1/offers/YOUR_OFFER_URL/stock \
-d '{"onDuplicate":"skip","option":"Premium","items":[{"Username":"player1","Password":"secret1","E-Mail":"[email protected]"}]}'
# Apply license keys (listing must have exactly one delivery field)
curl -s -X POST \
-H "Authorization: Bearer rmt_sk_live_…" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
https://rmt.gg/api/v1/offers/YOUR_OFFER_URL/stock \
-d '{"option":"Steam","keys":["AAAA-BBBB-CCCC","DDDD-EEEE-FFFF"]}'
# Or paste CSV / colon-separated rows in text
curl -s -X POST \
-H "Authorization: Bearer rmt_sk_live_…" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
https://rmt.gg/api/v1/offers/YOUR_OFFER_URL/stock \
-d '{"option":"Premium","delimiter":",","text":"Username,Password,E-Mail\nplayer1,secret1,[email protected]"}'TypeScript Batch-Import (1.000 Zeilen pro Anfrage)
// Paste rmtFetch and withRetry from the Auth section first.
const CHUNK = 1000;
async function importRows(offerUrl: string, option: string, rows: Array<Record<string, string>>) {
for (let i = 0; i < rows.length; i += CHUNK) {
const items = rows.slice(i, i + CHUNK);
const preview = await rmtFetch<{
results: Array<{ wouldImport: number; skippedDuplicates: number; errors: string[] }>;
}>(`/offers/${offerUrl}/stock`, {
method: "POST",
body: JSON.stringify({ dryRun: true, onDuplicate: "skip", option, items }),
});
const row = preview.results[0];
if ((row?.errors?.length ?? 0) > 0) {
throw new Error(row.errors.join("; "));
}
await withRetry(() =>
rmtFetch(`/offers/${offerUrl}/stock`, {
method: "POST",
body: JSON.stringify({ onDuplicate: "skip", option, items }),
}),
);
}
}
// License keys: only when GET stock.fields has exactly one name
async function importKeys(offerUrl: string, option: string, keys: string[]) {
for (let i = 0; i < keys.length; i += CHUNK) {
await withRetry(() =>
rmtFetch(`/offers/${offerUrl}/stock`, {
method: "POST",
body: JSON.stringify({ option, keys: keys.slice(i, i + CHUNK) }),
}),
);
}
}
Was diese API schützt
Schlüssel benötigen offers:write, sind rate-limitiert und können nur deine eigenen Angebote nachfüllen. GET gibt niemals gespeicherte Anmeldeinformationen zurück. POST-Antworten geben Benutzername, Passwort oder Schlüsselwerte nicht wieder. Sende den Body über HTTPS in der Produktion und halte den API-Schlüssel in einer Umgebungsvariable.
Unter Windows, benutze curl.exe (nicht den curl Alias). Setze den -d JSON in Anführungszeichen, damit PowerShell ihn nicht aufteilt.
Bestellungen sind auf dein Verkäuferkonto beschränkt. Käuferabrechnungsdetails können gemäß den Datenschutzbestimmungen des Marktplatzes anonymisiert werden.
/api/v1/ordersUnterstützt limit, offset, status, q und sort (neueste, älteste, total_high, total_low).
Anfrage
limitoffsetstatusqsort| Name | In | Typ | Erforderlich | Limits | Beschreibung |
|---|---|---|---|---|---|
limit | query | number | Optional | 1-100, default 20 | Page size. |
offset | query | number | Optional | >= 0, default 0 | Skip this many rows. |
status | query | string | Optional | Max 32 | Filter by order status (for example PAID, DELIVERED, COMPLETED). |
q | query | string | Optional | Max 80 | Search reference or related text. |
sort | query | string | Optional | newest (default) | newest | oldest | total_high | total_low. |
/api/v1/orders/:uidGibt die Bestellung mit Positionen zurück. Verwende die öffentliche Bestell-UID.
Anfrage
uid| Name | In | Typ | Erforderlich | Beschreibung |
|---|---|---|---|---|
uid | path | string | erforderlich | Order.uid. |
/api/v1/orders/:uid/deliverManuelle Erfüllung. COMPLEX-Positionen müssen vollständig angehängt sein, wenn erforderlich. Gibt order.delivered aus.
Anfrage
uidevidence| Name | In | Typ | Erforderlich | Limits | Beschreibung |
|---|---|---|---|---|---|
uid | path | string | erforderlich | Order.uid. | |
evidence | body | string[] | Optional | HTTPS, max 10 | Optional screenshot or transfer-proof URLs. |
Jeder genehmigte Partner-Shop oder Backend kann Käufer zu einer RMT.GG-Zahlungsseite senden. Wir bleiben der Händler und behalten 4% des gesperrten Betrags.
Whitelist und Erfüllung
Gehe zu Einstellungen, Gehosteter Checkout, und erstelle dort einen API-Schlüssel und ein JSON-Webhooks. Nach der Zahlung senden wir checkout.completed. Lieferwerte bleiben in der RMT.GG-Bestätigung; sie sind nicht im Verkäufer-GET oder in Webhooks enthalten.
/api/v1/checkout/sessionsLeite Käufer zu einer gesperrten RMT.GG-Zahlungsseite. Ein Artikel: Betrag und itemName. Warenkorb: items[] mit Name und Betrag in jeder Zeile. Die Währung ist standardmäßig USD. Nach der Zahlung bleibt der Käufer auf RMT.GG, wenn es Lieferfelder zum Kopieren gibt. returnUrl führt zurück zum Shop; ohne Lieferung senden wir sie nach einem kurzen Countdown zurück. Beträge, Längen und andere Obergrenzen sind in der Spalte Limits.
{
amount: 10, // what the buyer pays
itemName: "Gold pack", // pay page heading
}Anfrage
amountcurrencyitemNametitledescriptionimageUrlitemsitems[].nameitems[].titleitems[].descriptionitems[].amountitems[].quantityitems[].imageUrlitems[].deliveryitems[].delivery[].nameitems[].delivery[].typeitems[].delivery[].valueemailreturnUrlcancelUrlinvoiceIdcategorySlugofferingmetadataIdempotency-Key| Name | In | Typ | Erforderlich | Limits | Beschreibung |
|---|---|---|---|---|---|
amount | body | number | Bedingt | > 0, max 1,000,000 | What the buyer pays. Required for a single item. With items[], omit it or send the line sum. Mismatch: 400 AMOUNT_MISMATCH. |
currency | body | string | Optional | Default USD | ISO 4217 code such as USD or EUR. |
itemName | body | string | Bedingt | Max 120 | Pay page heading. Required for a single item. Alias: title. With items[], defaults to the first line name. Missing: 400 ITEM_NAME_REQUIRED. |
title | body | string | Optional | Alias of itemName. If both are sent, itemName wins. | |
description | body | string | Optional | Max 200 | Copy under the heading. If omitted, the heading is reused. |
imageUrl | body | string | Optional | HTTPS, max 2048 | Product image, or fallback for lines without imageUrl. Invalid: 400 INVALID_IMAGE_URL. |
items | body | object[] | Bedingt | 1-20 lines, JSON max 48,000 | Locked cart. Required when amount is omitted. Buyers cannot change lines. Empty: 400 INVALID_ITEMS. |
items[].name | body | string | erforderlich | Max 120 | Line title. Alias: title. |
items[].title | body | string | Optional | Alias of items[].name. If both are sent, name wins. | |
items[].description | body | string | Optional | Max 200 | Line copy under the name. |
items[].amount | body | number | erforderlich | > 0, max 1,000,000 | Unit price. Session total is sum(amount * quantity). |
items[].quantity | body | number | Optional | 1-99, default 1 | Locked on the pay page. |
items[].imageUrl | body | string | Optional | HTTPS, max 2048 | Line image. Falls back to top-level imageUrl. |
items[].delivery | body | object[] | Optional | Max 16 fields | Shown after payment on RMT.GG. Seller GET and webhooks omit values. |
items[].delivery[].name | body | string | erforderlich | Max 80 | Field label, for example Code or Password. |
items[].delivery[].type | body | string | Optional | text, password, textarea | password is blurred until the buyer reveals it. Default text. |
items[].delivery[].value | body | string | erforderlich | Max 2048 | Field value. Numbers are stored as strings. Empty: 400 INVALID_DELIVERY. |
email | body | string | Optional | Invalid values ignored | Prefills the pay page. The buyer still confirms email before paying. |
returnUrl | body | string | Optional | HTTPS, max 2048 | Continue-to-shop after payment. Delivery fields keep the buyer on RMT.GG with a button. No delivery: we send them back after a short countdown. If omitted, there is no shop button. http://localhost is allowed for local shops. |
cancelUrl | body | string | Optional | HTTPS, max 2048 | Redirect if the buyer cancels or the session expires. If omitted, they stay on the pay page. |
invoiceId | body | string | Optional | Max 128 | Your shop id. Same payload returns the existing session. A different payload: 409 INVOICE_CONFLICT. |
categorySlug | body | string | Bedingt | With offering, or omit both | Public root slug such as games. Used for card, PayPal, and crypto labels, not the pay page title. Wrong pair: 400 INVALID_PSP_CATEGORY. |
offering | body | string | Bedingt | With categorySlug, or omit both | Catalog offering such as Mods. Mapped to labels like Games · Add-ons. |
metadata | body | object | Optional | Object, max 4096 chars | Stored on the session. Not returned on seller GET. |
Idempotency-Key | header | string | Optional | Max 128 | Replay header. Same key and payload returns the existing session. A different payload: 409 IDEMPOTENCY_CONFLICT. |
Antwort
uidstatuspaidamountcurrencyitemNamedescriptionemaillangreturnUrlcancelUrlinvoiceIdexternalInvoiceIdsourceexpiresAthostedUrlorderUiditemshosted_urlexpires_at| Name | In | Typ | Beschreibung |
|---|---|---|---|
uid | response | string | Session id. Same value as in hostedUrl / hosted_url. |
status | response | string | created | pending_payment | paid | canceled | expired | refunded | error. Unpaid sessions expire after 24 hours. |
paid | response | boolean | true only when status is paid. false for refunded, expired, canceled, and unpaid states. |
amount | response | number | Locked buyer total in major units. |
currency | response | string | ISO currency code stored on the session (for example USD). |
itemName | response | string | null | Pay page heading. |
description | response | string | Longer copy under the heading. |
email | response | string | null | Prefill or confirmed buyer email. Guest checkout placeholders are returned as null. |
lang | response | string | null | Buyer locale when known. Not a create-session field. |
returnUrl | response | string | null | Continue-to-shop URL stored on the session, or null. |
cancelUrl | response | string | null | Cancel/expiry redirect, or null. |
invoiceId | response | string | null | Your invoice id. Same value as externalInvoiceId. |
externalInvoiceId | response | string | null | Same as invoiceId (legacy alias). |
source | response | string | How the session was created. API sessions are "api". |
expiresAt | response | string | ISO timestamp. Unpaid checkouts cannot be completed after this time. |
hostedUrl | response | string | Pay page URL (same target as top-level hosted_url on create). |
orderUid | response | string | null | Marketplace order uid after payment. null until the session is paid. |
items | response | object[] | Locked lines: name, description, amount, quantity, imageUrl. Seller GET never includes delivery values. |
hosted_url | response | string | Pay page URL. Send the buyer here. Same target as hostedUrl. |
expires_at | response | string | ISO timestamp. Same value as expiresAt. |
/api/v1/checkout/sessions/:uidGibt die von dir erstellte Sitzung zurück. Verwende dies, wenn checkout.completed verzögert ist. paid ist nur dann true, wenn der Status bezahlt ist. Items enthalten niemals Lieferwerte.
Anfrage
uid| Name | In | Typ | Erforderlich | Beschreibung |
|---|---|---|---|---|
uid | path | string | erforderlich | Session uid returned at create time. |
Antwort
uidstatuspaidamountcurrencyitemNamedescriptionemaillangreturnUrlcancelUrlinvoiceIdexternalInvoiceIdsourceexpiresAthostedUrlorderUiditems| Name | In | Typ | Beschreibung |
|---|---|---|---|
uid | response | string | Session id. Same value as in hostedUrl / hosted_url. |
status | response | string | created | pending_payment | paid | canceled | expired | refunded | error. Unpaid sessions expire after 24 hours. |
paid | response | boolean | true only when status is paid. false for refunded, expired, canceled, and unpaid states. |
amount | response | number | Locked buyer total in major units. |
currency | response | string | ISO currency code stored on the session (for example USD). |
itemName | response | string | null | Pay page heading. |
description | response | string | Longer copy under the heading. |
email | response | string | null | Prefill or confirmed buyer email. Guest checkout placeholders are returned as null. |
lang | response | string | null | Buyer locale when known. Not a create-session field. |
returnUrl | response | string | null | Continue-to-shop URL stored on the session, or null. |
cancelUrl | response | string | null | Cancel/expiry redirect, or null. |
invoiceId | response | string | null | Your invoice id. Same value as externalInvoiceId. |
externalInvoiceId | response | string | null | Same as invoiceId (legacy alias). |
source | response | string | How the session was created. API sessions are "api". |
expiresAt | response | string | ISO timestamp. Unpaid checkouts cannot be completed after this time. |
hostedUrl | response | string | Pay page URL (same target as top-level hosted_url on create). |
orderUid | response | string | null | Marketplace order uid after payment. null until the session is paid. |
items | response | object[] | Locked lines: name, description, amount, quantity, imageUrl. Seller GET never includes delivery values. |
/api/v1/checkout/sessionsDasselbe Sitzungsobjekt wie GET nach uid. Übergebe die invoiceId, die du bei der Erstellung gesendet hast. Fehlend: 400 INVOICE_ID_REQUIRED. Unbekannt: 404 NOT_FOUND.
Anfrage
invoiceId| Name | In | Typ | Erforderlich | Limits | Beschreibung |
|---|---|---|---|---|---|
invoiceId | query | string | erforderlich | Max 128 | invoiceId from create. Missing: 400 INVOICE_ID_REQUIRED. Too long: 400 INVALID_INVOICE_ID. |
Antwort
uidstatuspaidamountcurrencyitemNamedescriptionemaillangreturnUrlcancelUrlinvoiceIdexternalInvoiceIdsourceexpiresAthostedUrlorderUiditems| Name | In | Typ | Beschreibung |
|---|---|---|---|
uid | response | string | Session id. Same value as in hostedUrl / hosted_url. |
status | response | string | created | pending_payment | paid | canceled | expired | refunded | error. Unpaid sessions expire after 24 hours. |
paid | response | boolean | true only when status is paid. false for refunded, expired, canceled, and unpaid states. |
amount | response | number | Locked buyer total in major units. |
currency | response | string | ISO currency code stored on the session (for example USD). |
itemName | response | string | null | Pay page heading. |
description | response | string | Longer copy under the heading. |
email | response | string | null | Prefill or confirmed buyer email. Guest checkout placeholders are returned as null. |
lang | response | string | null | Buyer locale when known. Not a create-session field. |
returnUrl | response | string | null | Continue-to-shop URL stored on the session, or null. |
cancelUrl | response | string | null | Cancel/expiry redirect, or null. |
invoiceId | response | string | null | Your invoice id. Same value as externalInvoiceId. |
externalInvoiceId | response | string | null | Same as invoiceId (legacy alias). |
source | response | string | How the session was created. API sessions are "api". |
expiresAt | response | string | ISO timestamp. Unpaid checkouts cannot be completed after this time. |
hostedUrl | response | string | Pay page URL (same target as top-level hosted_url on create). |
orderUid | response | string | null | Marketplace order uid after payment. null until the session is paid. |
items | response | object[] | Locked lines: name, description, amount, quantity, imageUrl. Seller GET never includes delivery values. |
Konfiguriere HTTPS-Endpunkte (oder Discord-Webhooks) in Einstellungen → Entwickler. RMT POSTs, wenn abonnierte Ereignisse ausgelöst werden.
JSON-Lieferumschlag
{
"id": "whd_…",
"type": "order.paid",
"created": "2026-07-23T12:00:00.000Z",
"data": {
"order": {
"uid": "ord_…",
"reference": "RMT-…",
"status": "PAID",
"url": "https://rmt.gg/orders/ord_…",
"items": [ /* line items with offer names */ ]
}
}
}Signierte Lieferheader
{
"X-RMT-Event": "order.paid",
"X-RMT-Delivery": "whd_…",
"X-RMT-Timestamp": "1710000000",
"X-RMT-Signature": "v1=abc123…"
}checkout.abgeschlossen Payload
{
"id": "whd_…",
"type": "checkout.completed",
"created": "2026-08-14T12:00:00.000Z",
"data": {
"checkout": {
"uid": "pcs_…",
"status": "paid",
"amount": 10,
"currency": "USD",
"itemName": "Gold pack",
"description": "1000 gold for account example",
"invoiceId": "inv-12345",
"source": "api",
"orderUid": "ord_…",
"hostedUrl": "https://rmt.gg/pay/pcs_…",
"email": "[email protected]",
"paidAt": "2026-08-14T12:01:00.000Z",
"expiresAt": "2026-08-15T12:00:00.000Z",
"createdAt": "2026-08-14T12:00:00.000Z",
"reason": null,
"items": [
{
"name": "Gold pack",
"description": "1000 gold for account example",
"amount": 10,
"quantity": 1,
"imageUrl": "https://cdn.shop.example/gold.png"
}
]
}
}
}checkout.canceled Payload
{
"id": "whd_…",
"type": "checkout.canceled",
"created": "2026-08-14T12:20:00.000Z",
"data": {
"checkout": {
"uid": "pcs_…",
"status": "canceled",
"amount": 10,
"currency": "USD",
"itemName": "Gold pack",
"description": "1000 gold for account example",
"invoiceId": "inv-12345",
"source": "api",
"orderUid": null,
"hostedUrl": "https://rmt.gg/pay/pcs_…",
"email": "[email protected]",
"paidAt": null,
"expiresAt": "2026-08-15T12:00:00.000Z",
"createdAt": "2026-08-14T12:00:00.000Z",
"reason": "buyer_canceled",
"items": [
{
"name": "Gold pack",
"description": "1000 gold for account example",
"amount": 10,
"quantity": 1,
"imageUrl": "https://cdn.shop.example/gold.png"
}
]
}
}
}Wenn ein Signierungsgeheimnis festgelegt ist, berechne HMAC-SHA256 über timestamp + '.' + rawBody und vergleiche mit dem Hex nach v1=.
Das Geheimnis bleibt bei RMT. Jeder signierte POST enthält X-RMT-Timestamp (Unix-Sekunden) und X-RMT-Signature (v1= plus hex). Berechne HMAC-SHA256 über den String timestamp + '.' + rawBody mit deinem Geheimnis und vergleiche dann mit dem Hex nach v1=. Lehne Zeitstempel, die älter als 5 Minuten sind, ab.
TypeScript-Überprüfung (zeit-sichere Vergleiche und 5-Minuten-Wiederholungsfenster)
import { createHmac, timingSafeEqual } from "node:crypto";
const MAX_AGE_SEC = 5 * 60; // reject replays older than 5 minutes
export function verifyRmtSignature(opts: {
secret: string;
timestamp: string | null | undefined;
signatureHeader: string | null | undefined;
rawBody: string; // exact POST bytes. Do not JSON.parse then re-stringify.
nowSec?: number;
}): boolean {
const secret = opts.secret.trim();
const timestamp = String(opts.timestamp ?? "").trim();
const provided = String(opts.signatureHeader ?? "").trim().replace(/^v1=/i, "");
if (!secret || !timestamp || !provided) return false;
const ts = Number(timestamp);
if (!Number.isInteger(ts) || ts <= 0) return false;
const nowSec = opts.nowSec ?? Math.floor(Date.now() / 1000);
if (Math.abs(nowSec - ts) > MAX_AGE_SEC) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${opts.rawBody}`)
.digest("hex");
const a = Buffer.from(expected, "utf8");
const b = Buffer.from(provided.toLowerCase(), "utf8");
if (a.length !== b.length) return false;
return timingSafeEqual(a, b);
}
// Express / Node HTTP example:
// const rawBody = (req as { rawBody?: string }).rawBody
// ?? JSON.stringify(req.body); // only if you captured the raw string first
// const ok = verifyRmtSignature({
// secret: process.env.RMT_WEBHOOK_SECRET!,
// timestamp: req.headers["x-rmt-timestamp"] as string,
// signatureHeader: req.headers["x-rmt-signature"] as string,
// rawBody,
// });
// if (!ok) return res.status(401).end();
TypeScript Webhook-Handler
type CheckoutCompleted = {
id: string;
type: "checkout.completed";
created: string;
data: {
checkout: {
uid: string;
status: "paid";
amount: number;
currency: string;
itemName: string | null;
description: string;
invoiceId: string | null;
source: string | null;
orderUid: string | null;
hostedUrl: string;
email: string | null;
paidAt: string | null;
expiresAt: string | null;
createdAt: string | null;
reason: null;
items: Array<{
name: string;
description: string | null;
amount: number;
quantity: number;
imageUrl: string | null;
}>;
};
};
};
type CheckoutCanceled = {
type: "checkout.canceled";
data: {
checkout: {
uid: string;
status: "canceled" | "expired";
invoiceId: string | null;
reason: "buyer_canceled" | "expired";
};
};
};
export async function handleRmtWebhook(rawBody: string, headers: Headers) {
const ok = verifyRmtSignature({
secret: process.env.RMT_WEBHOOK_SECRET!,
timestamp: headers.get("x-rmt-timestamp"),
signatureHeader: headers.get("x-rmt-signature"),
rawBody,
});
if (!ok) throw new Response("Unauthorized", { status: 401 });
const event = JSON.parse(rawBody) as { type: string; data: Record<string, unknown> };
switch (event.type) {
case "checkout.completed": {
const checkout = (event as CheckoutCompleted).data.checkout;
if (!checkout.invoiceId || !checkout.paidAt) break;
await fulfillShopOrder(checkout.invoiceId, checkout.orderUid);
break;
}
case "checkout.canceled": {
const checkout = (event as CheckoutCanceled).data.checkout;
await markShopOrderCanceled(checkout.invoiceId, checkout.reason);
break;
}
case "checkout.refunded":
case "order.paid":
case "order.delivered":
break;
default:
break;
}
}
Für COMPLEX (einzigartige Einheit) Angebote kann RMT dein HTTPS-Endpunkt nach der Zahlung POSTen, um die nächste Lizenz, das Konto oder den Schlüssel zu minten, wenn der lokale Bestand knapp ist.
Zahlungssichere Fehler
Wenn dein Endpunkt zeitlich begrenzt ist oder ungültige Daten zurückgibt, bleibt die Bestellung BEZAHLT. Der Käufer wird belastet; du siehst einen Fehler bei der Bestellung und kannst die Reservierung erneut versuchen oder Schlüssel manuell anhängen.
Kanonicischer POST-Körper (gekürzt)
{
"id": "rsv_…",
"type": "reserve.item",
"order": { "uid": "ord_…", "reference": "RMT-…", "url": "https://rmt.gg/orders/ord_…" },
"offer": { "url": "my-offer", "title": "Game key", "pageUrl": "https://rmt.gg/offers/my-offer" },
"option": { "id": 1, "name": "Standard" },
"fields": [{ "id": 10, "name": "License", "type": "text", "required": true }],
"quantity": 1
}Anforderungsheader (wenn ein Signing-Geheimnis gesetzt ist)
{
"Content-Type": "application/json",
"X-RMT-Event": "reserve.item",
"X-RMT-Delivery": "rsv_…",
"X-RMT-Timestamp": "1710000000",
"X-RMT-Signature": "v1=abc123…"
}Bequeme Antwort
{
"entries": [
{ "name": "License", "value": "AAAA-BBBB-CCCC" }
]
}Zuordnungs-JSON-Felder (mit responseMap-Pfaden wie $.license)
{
"license": "AAAA-BBBB-CCCC",
"email": "[email protected]",
"password": "temporary-pass"
}Wenn du ein Geheimnis für das Angebot festlegst, ist jeder Reserve-POST signiert. Berechne HMAC-SHA256(secret, timestamp + '.' + rawBody) neu und vergleiche mit X-RMT-Signature, nachdem du das v1= Präfix entfernt hast. Das Geheimnis selbst ist niemals in der Anfrage enthalten.
Siehe vollständiges VerifizierungsbeispielTypeScript Reserve-Handler (überprüfen und dann Einträge zurückgeben)
type ReserveRequest = {
id: string;
type: "reserve.item";
dryRun?: boolean;
quantity: number;
fields: Array<{ name: string; required?: boolean }>;
};
export async function handleReserve(rawBody: string, headers: Headers) {
const ok = verifyRmtSignature({
secret: process.env.RMT_RESERVE_SECRET!,
timestamp: headers.get("x-rmt-timestamp"),
signatureHeader: headers.get("x-rmt-signature"),
rawBody,
});
if (!ok) return new Response("Unauthorized", { status: 401 });
const body = JSON.parse(rawBody) as ReserveRequest;
if (body.type !== "reserve.item") {
return Response.json({ error: "Unexpected event" }, { status: 400 });
}
const qty = Number(body.quantity);
if (!Number.isInteger(qty) || qty < 1) {
return Response.json({ error: "Invalid quantity" }, { status: 400 });
}
if (body.dryRun) {
return Response.json({
entries: [{ name: "License", value: "TEST-AAAA-BBBB" }],
});
}
const license = await mintLicense(); // your inventory
return Response.json({
entries: [{ name: "License", value: license }],
});
}
Rufe die Reservierung nicht vor der Zahlung auf
RMT ruft deinen Endpunkt nur nach erfolgreicher Zahlung auf, sodass abgebrochene Bestellungen keine Lizenzen verbrauchen.
Fehler geben JSON { error, code? } zurück. Open API-Verkehr ist auf 300 Anfragen pro Minute pro API-Schlüssel begrenzt.
API_KEY_REQUIREDFehlender Authorization- oder X-Api-Key-Header.
API_KEY_INVALIDSchlüssel unbekannt, widerrufen, abgelaufen oder Entwicklerzugang ausgesetzt.
SCOPE_MISSINGSchlüssel hat nicht den für den Endpunkt erforderlichen Scope.
RATE_LIMITEDZu viele Anfragen. Beachte Retry-After und X-RateLimit-Reset.
CHECKOUT_PARTNER_NOT_APPROVEDDieser Verkäufer ist nicht für den gehosteten Checkout genehmigt.
INVALID_JSONDer Anfragekörper muss JSON sein.
INVALID_AMOUNTDer Betrag muss größer als 0 und maximal 1.000.000 sein.
UNSUPPORTED_CURRENCYDie Währung ist kein unterstützter ISO-Code.
INVALID_RETURN_URLreturnUrl und cancelUrl müssen https sein (http://localhost ist für lokale Shops erlaubt).
INVALID_PSP_CATEGORYcategorySlug und offering müssen zusammen gesendet werden und ein Katalogpaar entsprechen.
INVALID_INVOICE_IDinvoiceId ist länger als 128 Zeichen.
INVOICE_ID_REQUIREDGET /checkout/sessions benötigt invoiceId als Abfrageparameter.
INVALID_IDEMPOTENCY_KEYIdempotency-Key ist länger als 128 Zeichen.
INVALID_METADATAmetadata muss ein JSON-Objekt sein, kein Array oder primitiver Typ.
METADATA_TOO_LARGESerialisierte Metadaten sind größer als 4096 Zeichen.
IDEMPOTENCY_CONFLICTIdempotency-Key wurde mit einem anderen Betrag, Währung oder Artikel wiederverwendet.
INVOICE_CONFLICTinvoiceId wurde mit einem anderen Betrag, Währung oder Artikel wiederverwendet.
INVALID_IMAGE_URLimageUrl muss eine https-URL sein.
ITEM_NAME_REQUIREDitemName (oder Titel) ist erforderlich, wenn items weggelassen wird.
INVALID_ITEMSitems müssen ein nicht leeres Array von gesperrten Positionen (max 20) sein. Jede Zeile benötigt einen Namen und einen Betrag.
TOO_MANY_ITEMSitems dürfen nicht mehr als 20 Zeilen enthalten.
AMOUNT_MISMATCHBetrag muss der Summe jedes Zeilenbetrags multipliziert mit der Menge entsprechen.
INVALID_DELIVERYLieferfelder sind ungültig. Jedes Feld benötigt einen Namen (max 80) und einen Wert (max 2048). type muss text, password oder textarea sein (Standard ist text). Maximal 16 Felder pro Zeile.
ITEMS_TOO_LARGEDie serialisierten Items im JSON sind größer als 48.000 Zeichen.
NOT_FOUNDKeine gehostete Checkout-Sitzung entspricht dieser uid oder invoiceId für diesen Verkäufer.
RESERVE_FAILEDWebhook-Reservierung hat zeitlich begrenzt, ungültige Daten zurückgegeben oder erforderliche Felder verpasst.
OPTION_AMBIGUOUSMehr als eine Preisoption entspricht diesem Namen. Übergeben Sie optionId von GET stock.
OPTION_NOT_FOUNDKeine Preisoption entspricht dieser ID oder diesem Namen in diesem Angebot.
OPTION_REQUIREDDieses Angebot hat mehrere Preisoptionen. Übergeben Sie option oder optionId.
UNKNOWN_FIELDEin Feldname stimmt nicht mit dem Liefer-Schema dieses Angebots überein.
FIELD_MAPPING_AMBIGUOUSKonnte Spalten oder Schlüssel nicht den Lieferfeldern zuordnen. Senden Sie Header oder verwenden Sie Artikelobjekte, die nach Feldnamen indiziert sind.
STOCK_MODE_MISMATCHDiese Nutzlast stimmt nicht mit dem Lager-Modus der Option überein (Menge vs. gespeicherte Artikel).
IMPORT_TOO_LARGEEine Nachfüllanfrage kann maximal 1.000 gespeicherte Artikel pro Option importieren.
OPTION_ITEM_CAPACITYDiese Preisoption hat bereits das Maximum von 5.000 unverkauften gespeicherten Artikeln erreicht.
DUPLICATE_ITEMSonDuplicate=error und mindestens ein Artikel existiert bereits in dieser Option.
UNLIMITED_STOCKDiese Option hat eine unbegrenzte Menge. Verwenden Sie set, um zuerst zu einer endlichen Anzahl zu wechseln.
INSUFFICIENT_STOCKNicht genügend Mengenbestand zum Entfernen.
STOCK_HELD_IN_CHECKOUTDie Menge kann nicht unter die aktuell im Checkout reservierten Einheiten gesenkt werden.
INVALID_RESTOCKDer Nachfüllkörper fehlt eine erforderliche Aktion oder kombiniert add/items in einer Option.
| Code | HTTP | Beschreibung |
|---|---|---|
| API_KEY_REQUIRED | 401 | Fehlender Authorization- oder X-Api-Key-Header. |
| API_KEY_INVALID | 401 | Schlüssel unbekannt, widerrufen, abgelaufen oder Entwicklerzugang ausgesetzt. |
| SCOPE_MISSING | 403 | Schlüssel hat nicht den für den Endpunkt erforderlichen Scope. |
| RATE_LIMITED | 429 | Zu viele Anfragen. Beachte Retry-After und X-RateLimit-Reset. |
| CHECKOUT_PARTNER_NOT_APPROVED | 403 | Dieser Verkäufer ist nicht für den gehosteten Checkout genehmigt. |
| INVALID_JSON | 400 | Der Anfragekörper muss JSON sein. |
| INVALID_AMOUNT | 400 | Der Betrag muss größer als 0 und maximal 1.000.000 sein. |
| UNSUPPORTED_CURRENCY | 400 | Die Währung ist kein unterstützter ISO-Code. |
| INVALID_RETURN_URL | 400 | returnUrl und cancelUrl müssen https sein (http://localhost ist für lokale Shops erlaubt). |
| INVALID_PSP_CATEGORY | 400 | categorySlug und offering müssen zusammen gesendet werden und ein Katalogpaar entsprechen. |
| INVALID_INVOICE_ID | 400 | invoiceId ist länger als 128 Zeichen. |
| INVOICE_ID_REQUIRED | 400 | GET /checkout/sessions benötigt invoiceId als Abfrageparameter. |
| INVALID_IDEMPOTENCY_KEY | 400 | Idempotency-Key ist länger als 128 Zeichen. |
| INVALID_METADATA | 400 | metadata muss ein JSON-Objekt sein, kein Array oder primitiver Typ. |
| METADATA_TOO_LARGE | 400 | Serialisierte Metadaten sind größer als 4096 Zeichen. |
| IDEMPOTENCY_CONFLICT | 409 | Idempotency-Key wurde mit einem anderen Betrag, Währung oder Artikel wiederverwendet. |
| INVOICE_CONFLICT | 409 | invoiceId wurde mit einem anderen Betrag, Währung oder Artikel wiederverwendet. |
| INVALID_IMAGE_URL | 400 | imageUrl muss eine https-URL sein. |
| ITEM_NAME_REQUIRED | 400 | itemName (oder Titel) ist erforderlich, wenn items weggelassen wird. |
| INVALID_ITEMS | 400 | items müssen ein nicht leeres Array von gesperrten Positionen (max 20) sein. Jede Zeile benötigt einen Namen und einen Betrag. |
| TOO_MANY_ITEMS | 400 | items dürfen nicht mehr als 20 Zeilen enthalten. |
| AMOUNT_MISMATCH | 400 | Betrag muss der Summe jedes Zeilenbetrags multipliziert mit der Menge entsprechen. |
| INVALID_DELIVERY | 400 | Lieferfelder sind ungültig. Jedes Feld benötigt einen Namen (max 80) und einen Wert (max 2048). type muss text, password oder textarea sein (Standard ist text). Maximal 16 Felder pro Zeile. |
| ITEMS_TOO_LARGE | 400 | Die serialisierten Items im JSON sind größer als 48.000 Zeichen. |
| NOT_FOUND | 404 | Keine gehostete Checkout-Sitzung entspricht dieser uid oder invoiceId für diesen Verkäufer. |
| RESERVE_FAILED | 400 | Webhook-Reservierung hat zeitlich begrenzt, ungültige Daten zurückgegeben oder erforderliche Felder verpasst. |
| OPTION_AMBIGUOUS | 409 | Mehr als eine Preisoption entspricht diesem Namen. Übergeben Sie optionId von GET stock. |
| OPTION_NOT_FOUND | 404 | Keine Preisoption entspricht dieser ID oder diesem Namen in diesem Angebot. |
| OPTION_REQUIRED | 400 | Dieses Angebot hat mehrere Preisoptionen. Übergeben Sie option oder optionId. |
| UNKNOWN_FIELD | 400 | Ein Feldname stimmt nicht mit dem Liefer-Schema dieses Angebots überein. |
| FIELD_MAPPING_AMBIGUOUS | 400 | Konnte Spalten oder Schlüssel nicht den Lieferfeldern zuordnen. Senden Sie Header oder verwenden Sie Artikelobjekte, die nach Feldnamen indiziert sind. |
| STOCK_MODE_MISMATCH | 400 | Diese Nutzlast stimmt nicht mit dem Lager-Modus der Option überein (Menge vs. gespeicherte Artikel). |
| IMPORT_TOO_LARGE | 400 | Eine Nachfüllanfrage kann maximal 1.000 gespeicherte Artikel pro Option importieren. |
| OPTION_ITEM_CAPACITY | 400 | Diese Preisoption hat bereits das Maximum von 5.000 unverkauften gespeicherten Artikeln erreicht. |
| DUPLICATE_ITEMS | 409 | onDuplicate=error und mindestens ein Artikel existiert bereits in dieser Option. |
| UNLIMITED_STOCK | 400 | Diese Option hat eine unbegrenzte Menge. Verwenden Sie set, um zuerst zu einer endlichen Anzahl zu wechseln. |
| INSUFFICIENT_STOCK | 400 | Nicht genügend Mengenbestand zum Entfernen. |
| STOCK_HELD_IN_CHECKOUT | 400 | Die Menge kann nicht unter die aktuell im Checkout reservierten Einheiten gesenkt werden. |
| INVALID_RESTOCK | 400 | Der Nachfüllkörper fehlt eine erforderliche Aktion oder kombiniert add/items in einer Option. |
429 behandeln
Reduziere die Nutzung mit Retry-After Sekunden. Drehe keine Schlüssel, um Limits zu umgehen; das Limit gilt pro Schlüssel und ist für alle Verkäufer gleich.
Erfolgreiche Antworten enthalten X-RateLimit-Limit, X-RateLimit-Remaining und X-RateLimit-Reset.
Erstelle einen Schlüssel in den Entwicklereinstellungen und verbinde Discord oder Telegram unter Benachrichtigungen.