/api/v1ディスカバリードキュメント
スコープ、クォータ、Webhookイベント、および完全な操作カタログを返します。任意の有効なAPIキーが機能します。
出品者オープンAPIは、Discordアラート、在庫同期、Zapierスタイルの自動化、またはRMT.GGの上にカスタムバックオフィスを望む出品者のためのものです。
開発者設定でAPIキーを作成し、ディスカバリーを呼び出してライブカタログを表示します。
/api/v1スコープ、クォータ、Webhookイベント、および完全な操作カタログを返します。任意の有効なAPIキーが機能します。
すべての/api/v1リクエストでライブシークレットキーを送信します。HTTPSのみを推奨します。公開クライアントやブラウザバンドルにキーを埋め込まないでください。
推奨ヘッダー
Authorization: Bearer rmt_sk_live_<prefix>_<secret>代替ヘッダー
X-Api-Key: rmt_sk_live_<prefix>_<secret>再利用可能なTypeScriptクライアント(Bearer認証、型付きエラー、429リトライ)
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));
}
}
}
漏洩時にローテーション
キーが漏洩した場合、開発者設定で無効にし、新しいものを作成します。ライブ中は無効にする前に自動化を更新してください。
各APIキーはエンドポイントを制御するスコープを持っています。スコープが不足している場合、403 SCOPE_MISSINGが返されます。
offers:read: オファーをリストおよび取得します。offers:write: オファーを作成、更新、公開、削除します。orders:read: 出品者の注文をリストおよび取得します。orders:write: 注文を配達済みとしてマークします。webhooks:manage: 将来のオープンAPIウェブフック管理のために予約されています。今日、通知でDiscord/Telegramを設定し、開発者設定でJSONウェブフックを構成してください。checkout:write: ホスティングチェックアウトセッションの作成と読み取り。管理者承認済みのパートナーのチェックアウトが必要です。デフォルトキーのスコープ
新しいキーはoffers:read、offers:write、orders:read、およびorders:writeを受け取ります。アウトバウンドWebhookのCRUDは設定UI(セッション認証)に残ります。
オファー識別子は、公開URLスラグまたは数値IDを受け入れます。レスポンスには内部IDとsellerIdは含まれません。
PATCHで変更できないもの
ストック行、オプション価格、メディア、および属性は出品者エディタ(または将来のエンドポイント)で管理されており、現在はPATCH経由ではありません。
/api/v1/offersarchive=active(デフォルト)、archived、またはallでフィルタリングします。
リクエスト
archive| 名前 | イン | タイプ | 必須 | 説明 |
|---|---|---|---|---|
archive | query | string | 任意 | One of "active" (default), "archived", or "all". |
/api/v1/offers認証された出品者が所有する空のドラフトを作成します。ボディは必要ありません。
/api/v1/offers/:urlOrId公開URLスラグまたは数値IDでロードします。関係(オプション)が含まれる場合がありますが、ストックアイテムは含まれません。
リクエスト
urlOrId| 名前 | イン | タイプ | 必須 | 説明 |
|---|---|---|---|---|
urlOrId | path | string | 必須 | Offer.url slug or Offer.id. |
/api/v1/offers/:urlOrIdリスティングフィールドの安全なサブセットをPATCHします。アウトバウンドWebhookが構成されている場合、offer.updatedが発生します。
リクエスト
urlOrIdtitledescriptionvisibilitycategoryIdofferingIdthumbnailofferTypelistingMode| 名前 | イン | タイプ | 必須 | 説明 |
|---|---|---|---|---|
urlOrId | path | string | 必須 | Offer.url slug or Offer.id. |
title | body | string | 任意 | Listing title. |
description | body | string | 任意 | Listing description. |
visibility | body | string | 任意 | PUBLIC | PRIVATE | UNPUBLISHED. |
categoryId | body | number | 任意 | Catalog category id. |
offeringId | body | number | 任意 | Catalog offering id. |
thumbnail | body | string | 任意 | Thumbnail URL or asset reference. |
offerType | body | string | 任意 | Offer type string used by the listing. |
listingMode | body | string | 任意 | Listing mode (for example STANDARD, RANK_BOOST, SESSION). |
/api/v1/offers/:urlOrId出品者UIと同じ削除/アーカイブルールです。
リクエスト
urlOrId| 名前 | イン | タイプ | 必須 | 説明 |
|---|---|---|---|---|
urlOrId | path | string | 必須 | Offer.url slug or Offer.id. |
/api/v1/offers/:urlOrId/publishドラフトを公開(または可視性を変更)します。必要なリスティングフィールドが不完全な場合、400で失敗します。
リクエスト
urlOrIdvisibility| 名前 | イン | タイプ | 必須 | 説明 |
|---|---|---|---|---|
urlOrId | path | string | 必須 | Offer.url slug or Offer.id. |
visibility | body | string | 任意 | Optional. PUBLIC (default), PRIVATE, or UNPUBLISHED. |
ティアごとの購入可能な数量を確認し、右側のリストに合った配信フィールド名を一致させてから、数量や保存されたキーとアカウントを再入荷します。
マッチングの仕組み
GET /api/v1/stock?fields=username,password は、これらのフィールドを持つスキーマのリストを見つけます。オプション名(またはoptionId)とフィールド名を使って再入荷します。内部フィールドIDは必要ありません。レスポンスには認証情報の値は含まれません。
/api/v1/stockティアごとの数量と配信フィールド名を返し、キーとアカウントを正しいオファーに一致させることができます。q、fields、stockMode、lowStockでフィルタリングします。認証情報の値は決して返しません。
リクエスト
qfieldsstockModelowStockarchive| 名前 | イン | タイプ | 必須 | 制限 | 説明 |
|---|---|---|---|---|---|
q | query | string | 任意 | Max 80 | Filter by listing title or url slug. |
fields | query | string | 任意 | Comma-separated delivery field names. The listing must have all of them (Username,Password). Names match case-insensitively. | |
stockMode | query | string | 任意 | QUANTITY or COMPLEX. Listing must have at least one option in that mode. | |
lowStock | query | number | 任意 | Keep listings that have a finite tier with available less than or equal to this number. | |
archive | query | string | 任意 | One of "active" (default), "archived", or "all". |
/api/v1/offers/:urlOrId/stockインデックスと同じStockOfferの形状で、1つのURLまたは数値IDのためのものです。数量のみ。
リクエスト
urlOrId| 名前 | イン | タイプ | 必須 | 説明 |
|---|---|---|---|---|
urlOrId | path | string | 必須 | Offer.url slug or Offer.id. |
/api/v1/offers/:urlOrId/stock数量ティア:追加、削除、または設定。保存アイテムティア:フィールド名によるアイテムオブジェクト、1つのフィールドがある場合はkeys[]、または区切られたテキスト。options[]を介して1回の呼び出しで複数のティア。dryRunはマッチングをプレビューします。onDuplicateはデフォルトでスキップします。
{
"option": "1 Month",
"add": 50
}リクエスト
urlOrIdoptionoptionIdaddremovesetitemskeystextdelimiterheadersoptionsdryRunonDuplicate| 名前 | イン | タイプ | 必須 | 制限 | 説明 |
|---|---|---|---|---|---|
urlOrId | path | string | 必須 | Offer.url slug or Offer.id. | |
option | body | string | 条件付き | Pricing option name (case-insensitive). Omit when the listing has a single tier. | |
optionId | body | number | 条件付き | Pricing option id from GET stock. Wins over option when both are sent. Ambiguous names return 409 OPTION_AMBIGUOUS. | |
add | body | number | 条件付き | 1-1,000,000 | QUANTITY: add this many units. Fails with 400 UNLIMITED_STOCK if the tier is unlimited. |
remove | body | number | 条件付き | 1-1,000,000 | QUANTITY: withdraw this many units. Fails with 400 INSUFFICIENT_STOCK when there is not enough. |
set | body | number | null | 条件付き | QUANTITY: set an absolute count. null means unlimited. Cannot go below units held in checkout. | |
items | body | object[] | 条件付き | Max 1,000 | COMPLEX: objects keyed by delivery field name, for example { "Username": "a", "Password": "b" }. Names match case-insensitively. |
keys | body | string[] | 条件付き | Max 1,000 | COMPLEX: license keys when the listing has exactly one delivery field. Otherwise 400 FIELD_MAPPING_AMBIGUOUS. |
text | body | string | 条件付き | 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 | 任意 | Default : | Delimiter for text. Ignored unless text is sent. |
headers | body | string[] | 任意 | Optional column headers for text when the first line is data, not names. | |
options | body | object[] | 条件付き | Restock several tiers in one call. Each element is the same shape as a single-option body (option, add, items, …). | |
dryRun | body | boolean | 任意 | Preview matching and counts without writing. Default false. | |
onDuplicate | body | string | 任意 | skip (default) or error | COMPLEX: skip existing unsold fingerprints, or fail the request with 409 DUPLICATE_ITEMS. |
アカウント用には items[]、単一フィールドライセンスコード用には keys[] を使用して POST /api/v1/offers/:url/stock を利用します。リクエストごとに1,000行に分割してください。
リスティングに一致するペイロードを選択
最初に GET stock を呼び出してください。fields[] に名前が複数ある場合は、それらの名前(ユーザー名、パスワード、Eメール)でキー付けされた items オブジェクトを送信します。フィールドが1つだけの場合は、keys[] で十分です。数量リスティングには items ではなく add を使用します。
リクエストごとに1,000行。ティアごとに5,000未販売アイテム。1分あたり300リクエスト。デフォルトで重複はスキップされます。
accounts.json(アカウントごとに1つのオブジェクト)
[
{ "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(1行ごとに1つのライセンスキー)
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バッチインポート(リクエストごとに1,000行)
// 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) }),
}),
);
}
}
このAPIが保護するもの
キーには offers:write が必要で、レート制限があり、自分のリスティングのみを再補充できます。GET は保存された認証情報を返しません。POST のレスポンスにはユーザー名、パスワード、またはキーの値はエコーされません。プロダクション環境では HTTPS 経由でボディを送信し、APIキーは環境変数に保持してください。
Windowsでは、curl.exeを使用してください(curlエイリアスではありません)。-d JSONを引用符で囲んで、PowerShellが分割しないようにします。
注文は出品者アカウントにスコープされています。バイヤーの請求情報は、マーケットプレイスのプライバシールールに基づいて非表示にされる場合があります。
/api/v1/orderslimit、offset、status、q、およびsort(newest、oldest、total_high、total_low)をサポートします。
リクエスト
limitoffsetstatusqsort| 名前 | イン | タイプ | 必須 | 制限 | 説明 |
|---|---|---|---|---|---|
limit | query | number | 任意 | 1-100, default 20 | Page size. |
offset | query | number | 任意 | >= 0, default 0 | Skip this many rows. |
status | query | string | 任意 | Max 32 | Filter by order status (for example PAID, DELIVERED, COMPLETED). |
q | query | string | 任意 | Max 80 | Search reference or related text. |
sort | query | string | 任意 | newest (default) | newest | oldest | total_high | total_low. |
/api/v1/orders/:uid行アイテムを含む注文を返します。公開注文UIDを使用します。
リクエスト
uid| 名前 | イン | タイプ | 必須 | 説明 |
|---|---|---|---|---|
uid | path | string | 必須 | Order.uid. |
/api/v1/orders/:uid/deliver手動履行。COMPLEX行は、必要な場合は完全に添付されている必要があります。order.deliveredが発生します。
リクエスト
uidevidence| 名前 | イン | タイプ | 必須 | 制限 | 説明 |
|---|---|---|---|---|---|
uid | path | string | 必須 | Order.uid. | |
evidence | body | string[] | 任意 | HTTPS, max 10 | Optional screenshot or transfer-proof URLs. |
承認されたパートナーショップまたはバックエンドは、購入者をRMT.GGの支払いページに送信できます。私たちは記録された販売者として残り、ロックされた金額の4%を取得します。
許可リストと履行
設定の下で、ホストされたチェックアウトを適用し、そこでAPIキーとJSONウェブフックを作成します。支払い後、checkout.completedを発行します。配達値はRMT.GGの確認にのみ表示され、売り手のGETやウェブフックには含まれません。
/api/v1/checkout/sessions購入者をロックされたRMT.GGの支払いページに送ります。1つのアイテム: 金額とitemName。カート: items[]には各行に名前と金額が含まれます。通貨はデフォルトでUSDです。支払い後、配達情報をコピーする必要がある場合、購入者はRMT.GGに留まります。returnUrlはショップに続きます; 配達がない場合は短いカウントダウンの後に戻します。金額、長さ、その他の制限はLimits列に記載されています。
{
amount: 10, // what the buyer pays
itemName: "Gold pack", // pay page heading
}リクエスト
amountcurrencyitemNametitledescriptionimageUrlitemsitems[].nameitems[].titleitems[].descriptionitems[].amountitems[].quantityitems[].imageUrlitems[].deliveryitems[].delivery[].nameitems[].delivery[].typeitems[].delivery[].valueemailreturnUrlcancelUrlinvoiceIdcategorySlugofferingmetadataIdempotency-Key| 名前 | イン | タイプ | 必須 | 制限 | 説明 |
|---|---|---|---|---|---|
amount | body | number | 条件付き | > 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 | 任意 | Default USD | ISO 4217 code such as USD or EUR. |
itemName | body | string | 条件付き | 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 | 任意 | Alias of itemName. If both are sent, itemName wins. | |
description | body | string | 任意 | Max 200 | Copy under the heading. If omitted, the heading is reused. |
imageUrl | body | string | 任意 | HTTPS, max 2048 | Product image, or fallback for lines without imageUrl. Invalid: 400 INVALID_IMAGE_URL. |
items | body | object[] | 条件付き | 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 | 必須 | Max 120 | Line title. Alias: title. |
items[].title | body | string | 任意 | Alias of items[].name. If both are sent, name wins. | |
items[].description | body | string | 任意 | Max 200 | Line copy under the name. |
items[].amount | body | number | 必須 | > 0, max 1,000,000 | Unit price. Session total is sum(amount * quantity). |
items[].quantity | body | number | 任意 | 1-99, default 1 | Locked on the pay page. |
items[].imageUrl | body | string | 任意 | HTTPS, max 2048 | Line image. Falls back to top-level imageUrl. |
items[].delivery | body | object[] | 任意 | Max 16 fields | Shown after payment on RMT.GG. Seller GET and webhooks omit values. |
items[].delivery[].name | body | string | 必須 | Max 80 | Field label, for example Code or Password. |
items[].delivery[].type | body | string | 任意 | text, password, textarea | password is blurred until the buyer reveals it. Default text. |
items[].delivery[].value | body | string | 必須 | Max 2048 | Field value. Numbers are stored as strings. Empty: 400 INVALID_DELIVERY. |
email | body | string | 任意 | Invalid values ignored | Prefills the pay page. The buyer still confirms email before paying. |
returnUrl | body | string | 任意 | 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 | 任意 | HTTPS, max 2048 | Redirect if the buyer cancels or the session expires. If omitted, they stay on the pay page. |
invoiceId | body | string | 任意 | Max 128 | Your shop id. Same payload returns the existing session. A different payload: 409 INVOICE_CONFLICT. |
categorySlug | body | string | 条件付き | 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 | 条件付き | With categorySlug, or omit both | Catalog offering such as Mods. Mapped to labels like Games · Add-ons. |
metadata | body | object | 任意 | Object, max 4096 chars | Stored on the session. Not returned on seller GET. |
Idempotency-Key | header | string | 任意 | Max 128 | Replay header. Same key and payload returns the existing session. A different payload: 409 IDEMPOTENCY_CONFLICT. |
レスポンス
uidstatuspaidamountcurrencyitemNamedescriptionemaillangreturnUrlcancelUrlinvoiceIdexternalInvoiceIdsourceexpiresAthostedUrlorderUiditemshosted_urlexpires_at| 名前 | イン | タイプ | 説明 |
|---|---|---|---|
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/:uid作成したセッションを返します。checkout.completedが遅延している場合はこれを使用します。paidはステータスが支払い済みのときのみtrueになります。アイテムには配達値は含まれません。
リクエスト
uid| 名前 | イン | タイプ | 必須 | 説明 |
|---|---|---|---|---|
uid | path | string | 必須 | Session uid returned at create time. |
レスポンス
uidstatuspaidamountcurrencyitemNamedescriptionemaillangreturnUrlcancelUrlinvoiceIdexternalInvoiceIdsourceexpiresAthostedUrlorderUiditems| 名前 | イン | タイプ | 説明 |
|---|---|---|---|
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/sessionsuidによるGETと同じセッションオブジェクトです。作成時に送信したinvoiceIdを渡してください。欠落:400 INVOICE_ID_REQUIRED。未知:404 NOT_FOUND。
リクエスト
invoiceId| 名前 | イン | タイプ | 必須 | 制限 | 説明 |
|---|---|---|---|---|---|
invoiceId | query | string | 必須 | Max 128 | invoiceId from create. Missing: 400 INVOICE_ID_REQUIRED. Too long: 400 INVALID_INVOICE_ID. |
レスポンス
uidstatuspaidamountcurrencyitemNamedescriptionemaillangreturnUrlcancelUrlinvoiceIdexternalInvoiceIdsourceexpiresAthostedUrlorderUiditems| 名前 | イン | タイプ | 説明 |
|---|---|---|---|
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. |
設定 → 開発者でHTTPSエンドポイント(またはDiscord Webhook)を構成します。サブスクライブされたイベントが発火するとRMTがPOSTします。
JSON配信封筒
{
"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 */ ]
}
}
}署名された配信ヘッダー
{
"X-RMT-Event": "order.paid",
"X-RMT-Delivery": "whd_…",
"X-RMT-Timestamp": "1710000000",
"X-RMT-Signature": "v1=abc123…"
}チェックアウト完了ペイロード
{
"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ペイロード
{
"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"
}
]
}
}
}署名シークレットが設定されている場合、timestamp + '.' + rawBodyに対してHMAC-SHA256を計算し、v1=の後の16進数と比較します。
秘密鍵はRMTに保持されます。各署名付きPOSTにはX-RMT-Timestamp(Unix秒)とX-RMT-Signature(v1=プラス16進数)が含まれます。あなたの秘密鍵を使用して、文字列timestamp + '.' + rawBodyに対してHMAC-SHA256を計算し、v1=の後の16進数と比較します。5分以上前のタイムスタンプは拒否します。
TypeScript検証(タイミングセーフ比較と5分のリプレイウィンドウ)
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ウェブフックハンドラー
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;
}
}
COMPLEX(ユニークユニット)リスティングの場合、RMTは支払い後に次のライセンス、アカウント、またはキーをミントするためにあなたのHTTPSエンドポイントにPOSTできます。ローカル在庫が不足している場合。
支払い安全な失敗
エンドポイントがタイムアウトするか無効なデータを返すと、注文はPAIDのままです。バイヤーは請求され、注文にエラーが表示され、予約を再試行するか手動でキーを添付できます。
標準POSTボディ(切り詰め)
{
"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
}リクエストヘッダー(署名シークレットが設定されている場合)
{
"Content-Type": "application/json",
"X-RMT-Event": "reserve.item",
"X-RMT-Delivery": "rsv_…",
"X-RMT-Timestamp": "1710000000",
"X-RMT-Signature": "v1=abc123…"
}便利なレスポンス
{
"entries": [
{ "name": "License", "value": "AAAA-BBBB-CCCC" }
]
}マッピングされたJSONフィールド(レスポンスマップパスは$.licenseのようになります)
{
"license": "AAAA-BBBB-CCCC",
"email": "[email protected]",
"password": "temporary-pass"
}オファーにシークレットを設定した場合、すべてのreserve POSTは署名されます。HMAC-SHA256(secret, timestamp + '.' + rawBody)を再計算し、v1=プレフィックスを削除した後にX-RMT-Signatureと比較します。シークレット自体はリクエストに含まれません。
完全な検証例を参照してくださいTypeScriptリザーブハンドラー(検証後、エントリを返す)
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 }],
});
}
支払い前に予約を呼び出さないでください
RMTは支払いが成功した後にのみエンドポイントを呼び出すため、放棄されたチェックアウトはライセンスを消費しません。
エラーはJSON { error, code? }を返します。オープンAPIトラフィックは、APIキーごとに300リクエスト/分に制限されています。
API_KEY_REQUIREDAuthorizationまたはX-Api-Keyヘッダーが不足しています。
API_KEY_INVALIDキーが不明、取り消された、期限切れ、または開発者アクセスが停止されています。
SCOPE_MISSINGキーがエンドポイントに必要なスコープを欠いています。
RATE_LIMITEDリクエストが多すぎます。Retry-AfterとX-RateLimit-Resetを尊重してください。
CHECKOUT_PARTNER_NOT_APPROVEDこの販売者はホスティングチェックアウトの承認を受けていません。
INVALID_JSONリクエストボディはJSONでなければなりません。
INVALID_AMOUNT金額は0より大きく、最大1,000,000でなければなりません。
UNSUPPORTED_CURRENCY通貨はサポートされているISOコードではありません。
INVALID_RETURN_URLreturnUrlとcancelUrlはhttpsでなければなりません(http://localhostはローカルショップ用に許可されています)。
INVALID_PSP_CATEGORYcategorySlugとofferingは一緒に送信され、カタログペアと一致する必要があります。
INVALID_INVOICE_IDinvoiceIdは128文字を超えています。
INVOICE_ID_REQUIREDGET /checkout/sessionsはクエリパラメータとしてinvoiceIdが必要です。
INVALID_IDEMPOTENCY_KEYIdempotency-Keyは128文字を超えています。
INVALID_METADATAmetadataはJSONオブジェクトでなければならず、配列やプリミティブではありません。
METADATA_TOO_LARGEシリアライズされたmetadataは4096文字を超えています。
IDEMPOTENCY_CONFLICTIdempotency-Keyが異なる金額、通貨、またはアイテムで再利用されました。
INVOICE_CONFLICTinvoiceIdが異なる金額、通貨、またはアイテムで再利用されました。
INVALID_IMAGE_URLimageUrlはhttpsのURLでなければなりません。
ITEM_NAME_REQUIREDアイテム名(またはタイトル)は、アイテムが省略された場合に必要です。
INVALID_ITEMSitemsはロックされたラインアイテムの空でない配列でなければなりません(最大20)。各ラインにはnameとamountが必要です。
TOO_MANY_ITEMSitemsは20行を超えることはできません。
AMOUNT_MISMATCHamountは各行の金額と数量の合計と等しくなければなりません。
INVALID_DELIVERY配信フィールドが無効です。各フィールドにはname(最大80)とvalue(最大2048)が必要です。typeはtext、password、またはtextarea(デフォルトはtext)でなければなりません。1行あたり最大16フィールドです。
ITEMS_TOO_LARGEシリアライズされたアイテムのJSONが48,000文字を超えています。
NOT_FOUNDこの売り手に対するuidまたはinvoiceIdに一致するホステッドチェックアウトセッションはありません。
RESERVE_FAILED予約Webhookがタイムアウトした、無効なデータを返した、または必須フィールドが不足していました。
OPTION_AMBIGUOUSその名前に一致する価格オプションが複数あります。GET stockからoptionIdを渡してください。
OPTION_NOT_FOUNDこのリストにそのIDまたは名前に一致する価格オプションはありません。
OPTION_REQUIREDこのリストには複数の価格オプションがあります。optionまたはoptionIdを渡してください。
UNKNOWN_FIELDフィールド名がこのリストの配信スキーマと一致しません。
FIELD_MAPPING_AMBIGUOUS列またはキーを配信フィールドにマッピングできませんでした。ヘッダーを送信するか、フィールド名でキー付けされたアイテムオブジェクトを使用してください。
STOCK_MODE_MISMATCHそのペイロードはオプションのストックモード(数量対保存アイテム)と一致しません。
IMPORT_TOO_LARGE再入荷リクエストは、オプションごとに最大1,000の保存アイテムをインポートできます。
OPTION_ITEM_CAPACITYこの価格オプションにはすでに最大の5,000の未販売保存アイテムがあります。
DUPLICATE_ITEMSonDuplicate=errorで、少なくとも1つのアイテムがこのオプションにすでに存在します。
UNLIMITED_STOCKこのオプションには無制限の数量があります。まず有限のカウントに切り替えるためにsetを使用してください。
INSUFFICIENT_STOCK削除するための数量ストックが不足しています。
STOCK_HELD_IN_CHECKOUTチェックアウトで予約されている数量を下回ることはできません。
INVALID_RESTOCK再入荷ボディに必要なアクションが欠けているか、1つのオプションにadd/itemsが組み合わされています。
| コード | HTTP | 説明 |
|---|---|---|
| API_KEY_REQUIRED | 401 | AuthorizationまたはX-Api-Keyヘッダーが不足しています。 |
| API_KEY_INVALID | 401 | キーが不明、取り消された、期限切れ、または開発者アクセスが停止されています。 |
| SCOPE_MISSING | 403 | キーがエンドポイントに必要なスコープを欠いています。 |
| RATE_LIMITED | 429 | リクエストが多すぎます。Retry-AfterとX-RateLimit-Resetを尊重してください。 |
| CHECKOUT_PARTNER_NOT_APPROVED | 403 | この販売者はホスティングチェックアウトの承認を受けていません。 |
| INVALID_JSON | 400 | リクエストボディはJSONでなければなりません。 |
| INVALID_AMOUNT | 400 | 金額は0より大きく、最大1,000,000でなければなりません。 |
| UNSUPPORTED_CURRENCY | 400 | 通貨はサポートされているISOコードではありません。 |
| INVALID_RETURN_URL | 400 | returnUrlとcancelUrlはhttpsでなければなりません(http://localhostはローカルショップ用に許可されています)。 |
| INVALID_PSP_CATEGORY | 400 | categorySlugとofferingは一緒に送信され、カタログペアと一致する必要があります。 |
| INVALID_INVOICE_ID | 400 | invoiceIdは128文字を超えています。 |
| INVOICE_ID_REQUIRED | 400 | GET /checkout/sessionsはクエリパラメータとしてinvoiceIdが必要です。 |
| INVALID_IDEMPOTENCY_KEY | 400 | Idempotency-Keyは128文字を超えています。 |
| INVALID_METADATA | 400 | metadataはJSONオブジェクトでなければならず、配列やプリミティブではありません。 |
| METADATA_TOO_LARGE | 400 | シリアライズされたmetadataは4096文字を超えています。 |
| IDEMPOTENCY_CONFLICT | 409 | Idempotency-Keyが異なる金額、通貨、またはアイテムで再利用されました。 |
| INVOICE_CONFLICT | 409 | invoiceIdが異なる金額、通貨、またはアイテムで再利用されました。 |
| INVALID_IMAGE_URL | 400 | imageUrlはhttpsのURLでなければなりません。 |
| ITEM_NAME_REQUIRED | 400 | アイテム名(またはタイトル)は、アイテムが省略された場合に必要です。 |
| INVALID_ITEMS | 400 | itemsはロックされたラインアイテムの空でない配列でなければなりません(最大20)。各ラインにはnameとamountが必要です。 |
| TOO_MANY_ITEMS | 400 | itemsは20行を超えることはできません。 |
| AMOUNT_MISMATCH | 400 | amountは各行の金額と数量の合計と等しくなければなりません。 |
| INVALID_DELIVERY | 400 | 配信フィールドが無効です。各フィールドにはname(最大80)とvalue(最大2048)が必要です。typeはtext、password、またはtextarea(デフォルトはtext)でなければなりません。1行あたり最大16フィールドです。 |
| ITEMS_TOO_LARGE | 400 | シリアライズされたアイテムのJSONが48,000文字を超えています。 |
| NOT_FOUND | 404 | この売り手に対するuidまたはinvoiceIdに一致するホステッドチェックアウトセッションはありません。 |
| RESERVE_FAILED | 400 | 予約Webhookがタイムアウトした、無効なデータを返した、または必須フィールドが不足していました。 |
| OPTION_AMBIGUOUS | 409 | その名前に一致する価格オプションが複数あります。GET stockからoptionIdを渡してください。 |
| OPTION_NOT_FOUND | 404 | このリストにそのIDまたは名前に一致する価格オプションはありません。 |
| OPTION_REQUIRED | 400 | このリストには複数の価格オプションがあります。optionまたはoptionIdを渡してください。 |
| UNKNOWN_FIELD | 400 | フィールド名がこのリストの配信スキーマと一致しません。 |
| FIELD_MAPPING_AMBIGUOUS | 400 | 列またはキーを配信フィールドにマッピングできませんでした。ヘッダーを送信するか、フィールド名でキー付けされたアイテムオブジェクトを使用してください。 |
| STOCK_MODE_MISMATCH | 400 | そのペイロードはオプションのストックモード(数量対保存アイテム)と一致しません。 |
| IMPORT_TOO_LARGE | 400 | 再入荷リクエストは、オプションごとに最大1,000の保存アイテムをインポートできます。 |
| OPTION_ITEM_CAPACITY | 400 | この価格オプションにはすでに最大の5,000の未販売保存アイテムがあります。 |
| DUPLICATE_ITEMS | 409 | onDuplicate=errorで、少なくとも1つのアイテムがこのオプションにすでに存在します。 |
| UNLIMITED_STOCK | 400 | このオプションには無制限の数量があります。まず有限のカウントに切り替えるためにsetを使用してください。 |
| INSUFFICIENT_STOCK | 400 | 削除するための数量ストックが不足しています。 |
| STOCK_HELD_IN_CHECKOUT | 400 | チェックアウトで予約されている数量を下回ることはできません。 |
| INVALID_RESTOCK | 400 | 再入荷ボディに必要なアクションが欠けているか、1つのオプションにadd/itemsが組み合わされています。 |
429を処理する
Retry-After秒を使用してバックオフします。制限を回避するためにキーをローテーションしないでください。制限はキーごとであり、すべての出品者に対してフラットです。
成功したレスポンスにはX-RateLimit-Limit、X-RateLimit-Remaining、およびX-RateLimit-Resetが含まれます。