REST API — v1

Bayiler için Oyun Yükleme ve Kupon API'si

Oyun yüklemeleri, oyun bakiyesi ve kupon kodları için tek bir REST API: kataloğu okuyun, oyuncuyu doğrulayın, siparişi oluşturun ve sonucu webhook ile ya da durum sorgusuyla alın.

Bu, SHOP2TOPUP'ın arkasındaki host-to-host (H2H) yükleme API'sidir. HTTPS üzerinden çalışan sade bir JSON API'dir; Bearer kimlik doğrulaması, UUID idempotency anahtarları, yayımlanmış hız sınırları ve kararlı, makine tarafından okunabilir hata kodları içerir. Böylece bir mağaza, bir vitrin ya da bir bot, istemci kütüphanesi olmadan programatik biçimde dijital oyun kredisi satabilir. Aynı arayüz, 5+ yıllık kesintisiz işleyiş boyunca 3.000.000+ teslim edilen siparişi taşıdı.

Fiyatlandırma, ödeme yöntemleri ve iş ortağı hesabının kendisinin nasıl çalıştığı mı ilginizi çekiyor? Şunu okuyun: SHOP2TOPUP bayi programına genel bakış.

Hiçbir şey imzalamadan önce ilk çağrınız

Her uç nokta, tek bir başlık taşıyan sıradan bir HTTPS isteğidir. Kurulacak bir SDK, projenize dahil edilecek bir istemci kütüphanesi veya eklenecek bir derleme adımı yoktur. Aşağıdaki çağrıyı yapıştırın, anahtarınızı yerleştirin; terminalinizde canlı bir katalog yanıtı belirsin.

İstek — cURL
curl -s "https://shop2topup.com/api/endpoints/v1/catalog/categories?bigCategoryId=1" \
  -H "Authorization: Bearer YOUR_KEY_ID.YOUR_KEY_SECRET"
Yanıt — 200 OK
{
  "success": true,
  "categories": [
    {
      "id": 12,
      "name": "Free Fire",
      "description": "Garena Free Fire diamonds",
      "big_category_id": 1,
      "big_category_name": "Mobile Games"
    }
  ]
}

Temel URL

https://shop2topup.com/api/endpoints/v1

Belgelenen her yol bu temel adrese görelidir. Yanıtlar JSON'dur ve her zaman boolean bir success alanı taşır; böylece istemcinizdeki tek bir dal, mutlu yolu diğer her şeyden ayırır.

Hata zarfı

Hatalar her durum kodunda aynı biçimi korur. Sözleşmenin parçası olan error.code üzerinden dallanın; insanlar için yazılan ve her an yeniden ifade edilebilecek error.message üzerinden asla dallanmayın.

Hata yanıtı
{
  "success": false,
  "error": {
    "code": "PRICE_INCREASED",
    "message": "Price has increased beyond expected",
    "action": "FIX_INPUT",
    "retryable": false,
    "retry_after": null,
    "details": {
      "expected_unit_price": "0.950000",
      "current_unit_price": "0.980000"
    }
  }
}

API yüzeyinin tamamı tek ekranda

On bir uç nokta entegrasyonun tamamını kapsar: kataloğu okuyun, bir kalemi fiyatlandırın, oyuncuyu kontrol edin, siparişi verin ve sonrasında mutabık kılın. Her satır; tüm parametreleri, örnek isteği ve örnek yanıtı içeren tam referansa doğrudan bağlanır.

YöntemYolNe yaparReferans
GET/accountGet Account InfoReferansı okuyun
GET/catalog/big-categoriesList Big CategoriesReferansı okuyun
GET/catalog/categoriesList CategoriesReferansı okuyun
GET/catalog/subcategoriesList Subcategories (Products)Referansı okuyun
GET/catalog/subcategory/:itemId/priceGet Item PriceReferansı okuyun
GET/catalog/category/:categoryId/requirementsGet Category RequirementsReferansı okuyun
POST/player/validateValidate PlayerReferansı okuyun
POST/orders/createCreate OrderReferansı okuyun
GET/orders/:orderIdGet Order StatusReferansı okuyun
POST/orders/batchBatch Get OrdersReferansı okuyun
GET/ordersList OrdersReferansı okuyun

Ayrıca sipariş durumu webhook'ları — bu sayfanın ilerisinde ayrıntılı olarak belgelenmiştir.

Kimlik doğrulama ve erişim denetimi

Kimlik doğrulama tek bir HTTP başlığından ibarettir. Her istek, bayi panelinizin API Access sayfasından üretilen bir anahtar kimliği ile gizli anahtardan oluşan Bearer kimlik bilgisini taşıyan bir Authorization başlığı içerir. Anahtar kimliği hesabı tanımlar; gizli anahtar sahipliğinizi kanıtlar ve sunucu tarafında bir HMAC kontrolüyle doğrulanır.

Gizli anahtar yalnızca oluşturma anında bir kez gösterilir ve sonrasında bir daha alınamaz. Onu yığınınızda zaten kullandığınız gizli yöneticisine koyun, ortam değişkeni olarak enjekte edin ve sürüm kontrolünün dışında tutun. Kaybederseniz kurtarmazsınız, yenilersiniz.

OAuth yönlendirmesi, oturum çerezi, yenileme token'ı ve zamanlanacak hiçbir şey yoktur. Bu, otomatik entegrasyonlar için önemlidir: bir cron işi veya kuyruk işçisi aynı kimlik bilgisini yıllarca yenileme yolu olmadan taşıyabilir ve gecenin üçünde uyanmanıza neden olacak bir sona erme saati yoktur.

Bir anahtara IP izin listesi eklenebilir. Liste boş olmadığı anda, başka adreslerden gelen istekler herhangi bir iş mantığı çalışmadan önce IP_NOT_ALLOWED ve HTTP 403 ile reddedilir; böylece sızmış bir kimlik bilgisi kendi sunucularınızın dışında işe yaramaz. Yenileme bir göç değil, bir dağıtımdır: yeni anahtarı oluşturun, yayına alın, eskisini silin.

Anahtarlar tek bir bayi hesabına aittir ve o hesabın cüzdanından harcar; bu yüzden gizli anahtarı asla bir tarayıcıya, bir mobil pakete veya herkese açık bir depoya göndermeyin. API'yi kendi arka ucunuzdan çağırın, kendi ön yüzünüz de sizin arka ucunuzla konuşsun. Burada belgelenen her şey, sunucudan sunucuya bir çağıran varsayar.

İstek başlığı
Authorization: Bearer <keyId>.<secret>

Bir anahtarı tek çağrıda doğrulayın

GET /account bir kimlik bilgisinin sağlık kontrolüdür. Üç soruyu aynı anda yanıtlar: anahtar kimlik doğrulamasından geçiyor mu, hesap etkin mi ve cüzdan sipariş etmek üzere olduğunuz tutarı karşılıyor mu. Açılışta ve her toplu çalıştırmadan önce çağırın.

Kimlik doğrulama ve hesap referansını okuyun

Baştan sona eksiksiz bir entegrasyon

Çalışan bir entegrasyon dört adımdır. Aşağıdaki örnekler bu adımların hepsini canlı API üzerinde cURL, Node ve PHP ile çalıştırır. Kendi kimlik bilgilerinizi yerleştirin; oldukları gibi çalışırlar.

  1. 1

    Kataloğu okuyun

    Büyük kategorilerden kategorilere, oradan alt kategorilere inin; ardından satmak üzere olduğunuz kalemin güncel fiyatını okuyun. Ağacı önbelleğe alın, fiyatı taze çekin.

  2. 2

    Oyuncuyu doğrulayın

    Para hareket etmeden önce kimliği oyun içi bir ada ve bölgeye çözümleyin. Yazım hataları ve bölgeler arası yanlışlıklar burada yakalanır.

  3. 3

    Siparişi oluşturun

    Bir UUID üretin, saklayın, sonra order_id olarak gönderin. Bu sizin idempotency anahtarınızdır ve bir oluşturma isteğini yeniden denemenin tek güvenli yoludur.

  4. 4

    Teslimatı doğrulayın

    Sipariş durumu webhook'unu bekleyin; kendi bekleme pencerenizden sonra hâlâ pending kalan her şey için bir durum okumasına başvurun.

cURL — akışın tamamıKodu göster
export S2T_KEY="YOUR_KEY_ID.YOUR_KEY_SECRET"
export S2T_BASE="https://shop2topup.com/api/endpoints/v1"

# 1. Find the item you want to sell.
curl -s "$S2T_BASE/catalog/subcategories?categoryId=12" \
  -H "Authorization: Bearer $S2T_KEY"

# 2. Read the exact price you will be charged for it.
curl -s "$S2T_BASE/catalog/subcategory/999/price" \
  -H "Authorization: Bearer $S2T_KEY"

# 3. Confirm the player exists BEFORE any money moves.
curl -s -X POST "$S2T_BASE/player/validate" \
  -H "Authorization: Bearer $S2T_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sub_category_id": 999, "player_id": "123456789", "server": "Asia"}'

# 4. Create the order. order_id is YOUR uuid and YOUR idempotency key.
curl -s -X POST "$S2T_BASE/orders/create" \
  -H "Authorization: Bearer $S2T_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "order_id": "01912345-6789-7abc-8def-0123456789ab",
    "sub_category_id": 999,
    "quantity": 1,
    "requirements": { "player_id": "123456789", "server": "Asia" },
    "expected_unit_price": "0.950000"
  }'

# 5. Read the order back until it leaves "pending".
curl -s "$S2T_BASE/orders/01912345-6789-7abc-8def-0123456789ab" \
  -H "Authorization: Bearer $S2T_KEY"
Node.js — akışın tamamıKodu göster
// Node 18+ — no dependencies, global fetch and global crypto.
const BASE = 'https://shop2topup.com/api/endpoints/v1';
const KEY = process.env.S2T_KEY; // "<keyId>.<secret>", server-side only.

async function call(path, init = {}) {
  const res = await fetch(BASE + path, {
    ...init,
    headers: {
      Authorization: `Bearer ${KEY}`,
      'Content-Type': 'application/json',
      ...(init.headers || {}),
    },
  });

  if (res.status === 429) {
    const wait = Number(res.headers.get('Retry-After') || 5);
    await new Promise((r) => setTimeout(r, wait * 1000));
    return call(path, init);
  }

  const body = await res.json();
  if (!res.ok || body.success === false) {
    throw Object.assign(new Error(body?.error?.code || 'HTTP_' + res.status), {
      code: body?.error?.code,
      status: res.status,
    });
  }
  return body;
}

async function sell({ itemId, categoryId, playerId, server }) {
  // 1 + 2. Catalog and current price.
  const { subcategories } = await call(
    `/catalog/subcategories?categoryId=${categoryId}`,
  );
  const item = subcategories.find((s) => s.item_id === itemId);
  const { price } = await call(`/catalog/subcategory/${itemId}/price`);

  // 3. Player check before charging.
  const { player } = await call('/player/validate', {
    method: 'POST',
    body: JSON.stringify({
      sub_category_id: itemId,
      player_id: playerId,
      server,
    }),
  });

  // 4. Create the order. Persist orderId BEFORE the call so a crash mid-flight
  //    can be replayed with the same uuid instead of double-charging.
  const orderId = crypto.randomUUID();
  await saveIntent(orderId, itemId, playerId);

  const { order } = await call('/orders/create', {
    method: 'POST',
    body: JSON.stringify({
      order_id: orderId,
      sub_category_id: itemId,
      quantity: 1,
      requirements: { player_id: playerId, server },
      expected_unit_price: price.unit_price,
    }),
  });

  // 5. Confirm. A webhook usually beats the poll; the poll is the safety net.
  let status = order.status;
  while (status === 'pending') {
    await new Promise((r) => setTimeout(r, 20000));
    status = (await call(`/orders/${orderId}`)).order.status;
  }

  return { orderId, status, itemName: item?.name, playerName: player.player_name };
}
PHP — akışın tamamıKodu göster
<?php
// PHP 8 — plain cURL, no SDK required.
const S2T_BASE = 'https://shop2topup.com/api/endpoints/v1';

function s2t(string $path, ?array $body = null): array
{
    $ch = curl_init(S2T_BASE . $path);
    $headers = [
        'Authorization: Bearer ' . getenv('S2T_KEY'),
        'Content-Type: application/json',
    ];

    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => $headers,
        CURLOPT_TIMEOUT        => 30,
    ]);

    if ($body !== null) {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
    }

    $raw    = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $data = json_decode($raw, true) ?: [];

    if ($status >= 400 || ($data['success'] ?? false) === false) {
        throw new RuntimeException($data['error']['code'] ?? 'HTTP_' . $status);
    }

    return $data;
}

// 1 + 2. Catalog, then the exact price for the item.
$catalog = s2t('/catalog/subcategories?categoryId=12');
$price   = s2t('/catalog/subcategory/999/price');

// 3. Player check before charging.
$player = s2t('/player/validate', [
    'sub_category_id' => 999,
    'player_id'       => '123456789',
    'server'          => 'Asia',
]);

// 4. Create the order with your own uuid as the idempotency key.
$orderId = sprintf(
    '%04x%04x-%04x-7%03x-%04x-%04x%04x%04x',
    random_int(0, 0xffff), random_int(0, 0xffff),
    random_int(0, 0xffff), random_int(0, 0x0fff),
    random_int(0x8000, 0xbfff),
    random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0xffff)
);

$order = s2t('/orders/create', [
    'order_id'            => $orderId,
    'sub_category_id'     => 999,
    'quantity'            => 1,
    'requirements'        => ['player_id' => '123456789', 'server' => 'Asia'],
    'expected_unit_price' => $price['price']['unit_price'],
]);

// 5. Read it back until it settles.
do {
    sleep(20);
    $latest = s2t('/orders/' . $orderId);
} while ($latest['order']['status'] === 'pending');

echo $latest['order']['status'];

İki ayrıntıyı birebir kopyalamaya değer: sipariş UUID'sini oluşturma çağrısından sonra değil önce saklayın ve önbellekteki bir sayıya güvenmek yerine fiyatı yeniden okuyun. Bu iki alışkanlık, neredeyse her çifte tahsilatı ve her PRICE_INCREASED reddini ortadan kaldırır.

Webhook'lar: sipariş durumu geri çağrıları

Tek bir HTTPS uç noktası kaydedin; platform sipariş sonuçlarını gerçekleştikçe oraya gönderir, böylece mağazanız sorgulamayı bırakıp tepki vermeye başlar. Geri çağrı imzalıdır; yani üzerinde işlem yapmadan önce yükün bizden geldiğini kanıtlayabilirsiniz.

Olaylar

OlayNe zaman tetiklenir
order.completedA unit of the order was delivered. Read data.status for the whole order.
order.refundedA unit of the order was refunded to your wallet. data.status reads refunded once every unit is.
webhook.testYou triggered a signed test delivery from the panel to check your handler.

Teslimat başlıkları

BaşlıkDeğer
Content-Typeapplication/json
X-Shop2Topup-Signaturesha256=<hex> — HMAC-SHA256 of the raw request body
X-Shop2Topup-EventEvent name, e.g. order.completed
User-Agentshop2topup-webhook/1.0

Geri çağrı yükü

Her geri çağrının aynı üç üst düzey alanı vardır — event, timestamp ve data — ve data, durum uç noktasının döndürdüğü sipariş nesnesini yansıtır. Kupon kodları yalnızca tamamlanmış bir siparişte bulunur, player_name null olabilir ve sipariş adetlere bölündüğünde sub_transaction_summary görünür.

POST gövdesi — order.completed
{
  "event": "order.completed",
  "timestamp": "2026-03-11T14:30:00.000Z",
  "data": {
    "order_id": "01912345-6789-7abc-8def-0123456789ab",
    "status": "completed",
    "player_id": "123456789",
    "player_name": "ProGamer99",
    "subcategory_name": "FF 100 Diamonds",
    "quantity": 1,
    "charged_amount": "0.950000",
    "currency": "USD",
    "created_at": "2026-03-11T14:28:00.000Z",
    "completed_at": "2026-03-11T14:30:00.000Z",
    "vouchers": [
      {
        "code": "XXXX-YYYY-ZZZZ",
        "serial_number": "SN-9928371",
        "expiry_date": "2027-03-11"
      }
    ],
    "sub_transaction_summary": {
      "total": 1,
      "completed": 1,
      "failed": 0,
      "pending": 0,
      "processing": 0,
      "retrying": 0,
      "refunded": 0
    }
  }
}

İmza doğrulama

Her teslimat, sha256= ardından webhook gizli anahtarınızla ham istek gövdesinin hex HMAC-SHA256 değeri biçiminde bir X-Shop2Topup-Signature taşır. Herhangi bir JSON ayrıştırmasından önce ham baytlar üzerinden doğrulayın ve sabit zamanda karşılaştırın. Önce ayrıştırıp sonra doğrulayan bir işleyici, yeniden serileştirilmiş bir gövdeyle kandırılabilir.

Node.js — doğrula ve onayla
// Express — verify the signature against the RAW body, not the parsed object.
const crypto = require('crypto');

app.post(
  '/shop2topup/webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.get('X-Shop2Topup-Signature') || '';
    const expected =
      'sha256=' +
      crypto
        .createHmac('sha256', process.env.S2T_WEBHOOK_SECRET)
        .update(req.body)
        .digest('hex');

    const a = Buffer.from(expected);
    const b = Buffer.from(signature);
    if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
      return res.status(401).end();
    }

    const payload = JSON.parse(req.body.toString('utf8'));

    // Answer fast, then do the work. The delivery times out after 10 seconds.
    res.status(200).end();
    enqueueSettlement(payload.data.order_id, payload.event);
  },
);

Teslimat ve yeniden denemeler

Bir geri çağrı yalnızca bir kez denenir ve uç noktanızın yanıt vermek için on saniyesi vardır. Arkasında otomatik yeniden deneme veya üstel geri çekilme yoktur; bu bilinçlidir: yetkili kayıt durum uç noktasıdır, dolayısıyla mutabakat, göremediğiniz bir yeniden deneme takvimine değil sizin kontrol ettiğiniz bir sorgulamaya aittir. Hemen 200 döndürün ve işi kendi kuyruğunuzda yapın.

Idempotent işleyiciler

İşleyicinizi order_id üzerine kurun ve bir geri çağrının yinelenmesini etkisiz hale getirin. Tek teslimat denemesinde bile kendi altyapınız zaman zaman aynı mesajı iki işçiye verecektir; bir müşteriye iki kez kredi yükleyen sipariş, geç yükleyenden çok daha kötü bir hatadır.

Kayıt ve gizli anahtarlar

Geri çağrı URL'sini kaydetmek, imzalı bir test teslimatı göndermek ve imzalama gizli anahtarını yenilemek; herkese açık API üzerinden değil, oturum açtığınız bayi panelinin içinde yapılır. HTTPS zorunludur ve özel adresler kabul edilmez.

Bayi panelinde API access'i açın

Hız sınırları ve hata yönetimi

Sınırlar yayımlanmıştır, hesap bazındadır ve kayan pencereyle uygulanır. En geniş pay, okuma ağırlıklı katalog işlerine ayrılmıştır; durum uç noktaları bilinçli olarak dardır, çünkü bir sorgulama döngüsü değil, mutabakat yedeği olarak vardır.

RotaİstekPencereSayım birimi
GET /catalog/* (all five)500060sper account
POST /orders/create500060sper account
GET /orders/:orderId360sper account + order id
POST /orders/batch260sper account
GET /orders260sper account
GET /account6060sper account
POST /player/validatedaily player-check quota24hper account (no X-RateLimit headers)

Katalog, sipariş ve hesap uç noktaları her yanıtta X-RateLimit-Limit, X-RateLimit-Remaining ve X-RateLimit-Reset taşır; böylece düzgün davranan bir istemci nerede durduğunu tahmin etmek zorunda kalmaz. remaining sayacını izleyin ve kısıtlandıktan sonra değil, öncesinde yavaşlayın. Oyuncu doğrulaması istisnadır: günlük oyuncu kontrolü kotanızla çalışır, X-RateLimit başlığı göndermez ve bu kotayı 429 gövdesinde bildirir.

Kısıtlanmış yanıt — 429
HTTP/1.1 429 Too Many Requests
Retry-After: 45
X-RateLimit-Limit: 2
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1765000000

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Try again after retry_after seconds.",
    "action": "RETRY_LATER",
    "retryable": true,
    "retry_after": 45,
    "details": {
      "limit": 2,
      "remaining": 0,
      "retry_after": 45,
      "window_seconds": 60
    }
  }
}

İlk karşılaşacağınız hatalar ve çözümleri

Hatalar her zaman HTTP durumunun yanında kararlı bir kod döndürür. Bunlar, yeni bir entegrasyonun ilk haftasında karşılaştıklarıdır; her biri, sorunu çözen değişiklikle birlikte verilmiştir.

KodHTTPNe anlama gelirNasıl düzeltilir
INVALID_API_KEY401Invalid API keyThe provided API key does not match any active key in the system.Verify your key ID and secret are correct. Regenerate the key from the API Access panel if needed.action: FIX_INPUT · retryable: false · retry_after: null
IP_NOT_ALLOWED403IP address not in allowlistThe request originated from an IP address not in your API key allowlist.Add your server IP to the allowlist in the API Access panel, or remove IP restrictions.action: FIX_INPUT · retryable: false · retry_after: null
MISSING_REQUIRED_FIELD400Missing required fieldA required field is missing from the request body. For player requirements, details.field names the missing field.Check the endpoint documentation — and GET /catalog/category/:categoryId/requirements for player fields — then send every required field.action: FIX_INPUT · retryable: false · retry_after: null
INVALID_UUID_FORMAT400Invalid UUID formatThe order_id is not a valid UUID format.Use any valid UUID format (v1, v4, v7, etc). Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.action: FIX_INPUT · retryable: false · retry_after: null
PLAYER_NOT_FOUND400Player not foundThe game confirmed that this player ID does not exist. Only a confirmed answer returns this code — when the ID cannot be checked you get PLAYER_CHECK_UNAVAILABLE instead.Verify the player ID is correct. Check if a zone_id is required for the game.action: FIX_INPUT · retryable: false · retry_after: null
PLAYER_CHECK_UNAVAILABLE503Player IDs for this game can't be verified right nowPlayer IDs for this game cannot be checked at the moment. The player ID was NOT rejected.Try the same request again after retry_after seconds. Do not tell your customer the ID is wrong.action: RETRY_LATER · retryable: true · retry_after: 60
REGION_MISMATCH400This player's region can't receive this productThe player belongs to a region this product cannot be delivered to. The error carries player_region and player_name.Choose the product for the player's region.action: CHOOSE_OTHER · retryable: false · retry_after: null
INSUFFICIENT_BALANCE400Insufficient wallet balanceYour wallet does not have enough funds to complete this order.Top up your wallet from the Reload page before placing the order.action: TOP_UP · retryable: false · retry_after: null
OUT_OF_STOCK400Product is out of stockThe requested product is out of stock right now.Try again later or choose a different product.action: RETRY_LATER · retryable: true · retry_after: null
PRODUCT_UNAVAILABLE409This product is temporarily unavailableThe product exists but cannot be sold right now.Try again later, or choose a different product.action: RETRY_LATER · retryable: true · retry_after: null
DUPLICATE_ORDER409Duplicate order IDAn order with this UUID already exists. This is an idempotency protection.Generate a new UUID for a new order. Getting this on a retry means the first attempt went through: read that order with GET /orders/{orderId} instead of creating another.action: CHECK_ORDER · retryable: false · retry_after: null
PRICE_INCREASED400Price has increased beyond expectedThe current unit price is higher than the expected_unit_price you provided.Fetch the latest price with GET /catalog/subcategory/:id/price and update expected_unit_price.action: FIX_INPUT · retryable: false · retry_after: null
RATE_LIMIT_EXCEEDED429Rate limit exceededYou have exceeded the limit for this endpoint. The per-minute buckets (catalog, orders, account) send X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset on every response, and details repeats limit, remaining (0), retry_after and window_seconds. POST /player/validate is metered by your daily player-check quota instead: it sends no X-RateLimit headers, and its 429 carries limit, current, remaining, reset_at and reset_in_seconds inside error. Either way error.retry_after and the Retry-After header carry the seconds to wait.Wait for the number of seconds in retry_after or the Retry-After header before retrying. On the per-minute endpoints, watch X-RateLimit-Remaining to avoid hitting the limit at all.action: RETRY_LATER · retryable: true · retry_after: seconds to reset
INTERNAL_ERROR500Internal server errorAn unexpected error occurred on the server.Send the request again after retry_after seconds. For POST /orders/create reuse the same order_id, so the order can never be created twice. If it persists, contact support.action: RETRY_LATER · retryable: true · retry_after: 30
Tam hata kodu referansını görün — 42 hata kodunun tamamı, çözümleriyle

Entegrasyon soruları, yanıtlarıyla

Geliştiricilerin bunu kurarken gerçekten sorduğu sorular: kimlik doğrulama, yeniden denemeler, idempotency, kısıtlama, geri çağrılar ve hata yönetimi.

Bayi API'si istekleri nasıl doğruluyor?

Tek bir HTTP başlığıyla: Authorization: Bearer <keyId>.<secret>. Anahtar çifti bayi panelinizin API Access sayfasından üretilir, gizli anahtar yalnızca oluşturma anında bir kez gösterilir ve sunucu her çağrıda bunu bir HMAC kontrolüyle doğrular. OAuth yönlendirmesi, oturum çerezi veya yenilenecek bir token yoktur; aynı başlık hem ilk istekte hem de sonraki her istekte çalışır.

İstemcim hangi temel URL'yi ve sürümü hedeflemeli?

Tek bir temel URL var ve o da canlı olan: önce üzerinde geliştirip sonra değiştireceğiniz ayrı bir sandbox sunucusu yok. İstemciyi yukarıdaki v1 temel URL'sine yönlendirin, her istekte Authorization: Bearer <keyId>.<secret> gönderin ve her şeyden önce GET /account ile anahtarın ve başlık biçiminin doğruluğunu kanıtlayın. Para hareketi yaratan tek çağrı POST /orders/create olduğundan onu en sona bırakın, en küçük birim üzerinde deneyin ve istemci daha oturmamışken yanında expected_unit_price gönderin.

POST /orders/create üzerinde idempotency anahtarını hangi alan taşır?

order_id alanı — istemci tarafında ürettiğiniz ve oluşturma gövdesinde gönderdiğiniz bir UUID. Zaten var olan bir order_id ile oluşturmayı yinelerseniz API ikinci bir sipariş açmak yerine HTTP 409 ve DUPLICATE_ORDER döner; böylece yeniden deneme asla ikinci bir tahsilata dönüşmez, orijinal siparişi GET /orders/:orderId ile okursunuz. UUID'yi ilk denemeden önce üretip saklayın: zaman aşımından sonra üretilen UUID yeni bir anahtardır, yeniden deneme anahtarı değil, ve sunucu aynı şeyi kasteden iki order_id değerini sizin için birleştirmez.

İsteğim zaman aşımına uğradı. Sipariş oluştu mu?

Tahmin etmek yerine durum okuyarak öğrenin. Gönderdiğiniz UUID ile GET /orders/:orderId çağırın: sipariş geri geliyorsa zaman aşımı, sipariş kabul edildikten sonra yaşanmıştır ve yeniden göndermemelisiniz. ORDER_NOT_FOUND alıyorsanız hiçbir tahsilat yapılmamıştır ve oluşturma isteğini aynı UUID ile yinelemek güvenlidir.

Bir webhook çağrısının imzasını nasıl doğrularım?

HMAC'i ham istek gövdesi üzerinden yeniden hesaplayıp X-Shop2Topup-Signature başlığıyla karşılaştırın. Başlık sha256=<hex> taşır: aldığınız baytların tam olarak kendisini webhook gizli anahtarınızla HMAC-SHA256'dan geçirin, başına sha256= ekleyin ve sabit zamanlı karşılaştırma yapın — asla yeniden serileştirilmiş bir JSON nesnesiyle değil, çünkü ayrıştırıp yeniden yazmak baytları değiştirir ve karşılaştırma hiçbir zaman tutmaz. Tutmayan isteği 401 ile reddedin, geçerli teslimata on saniye içinde 200 dönün ve mutabakat işini sonrasında kendi kuyruğunuzda yapın.

Hız sınırına takılırsam ne olur?

RATE_LIMIT_EXCEEDED kodu ve bir Retry-After başlığıyla HTTP 429 alırsınız. Yanıt gövdesi aynı değeri retry_after alanında yineler ve limit, remaining ile window_seconds ekler. Tam olarak o kadar saniye bekleyip bir kez yeniden deneyin; hemen yeniden denemeyin ve aynı çağrıyı birkaç anahtara yaymayın, çünkü sayaç bağlantıya değil hesaba göre tutulur.

POST /player/validate hangi alanları döner?

player_name alanını, oyun sunuyorsa zone_id ve region alanlarıyla birlikte, yanıtın data nesnesi içinde döner. Sipariş oluşturma oyuncuyu kendisi çözer ve bölge kontrolünü kendisi yapar; bölge dışı bir deneme, cüzdandan herhangi bir tahsilat olmadan önce REGION_MISMATCH (HTTP 400) ile reddedilir, yani bakiyenizi koruyan şey bu çağrı değildir. Yine de çağırın: player_name değerini geri okumak, alıcıya yükleme yapacağı hesabı göstermenin ve o onaylamadan önce yanlış yazılmış bir kimliği yakalamanın tek pratik yoludur — ad her zaman oyundan gelir, alıcının yazdığından değil.

requirements yanıtını sipariş gövdesine nasıl dönüştürürüm?

field_name üzerinden eşleyin: GET /catalog/category/:categoryId/requirements yanıtındaki her kayıt, POST /orders/create isteğinde gönderdiğiniz requirements nesnesinin bir alanı olur. Her kayıt ayrıca data_type taşır; formunuz neyi çizeceğini, sunucunuz neye dönüştüreceğini böyle bilir. single_select ve multi_select kayıtları izin verilen değerleri de getirir — serbest metin kutusu yerine bunları sunun. Listeyi her sipariş yerine kategori başına önbelleğe alın ve bir oluşturma çağrısı MISSING_REQUIRED_FIELD ile dönerse yeniden okuyun.

İstemcim PRICE_INCREASED yanıtını nasıl ele almalı?

Fiyatı yeniden okuyun, karar verin, sonra yeniden gönderin — aynı gövdeyi asla tekrarlamayın. PRICE_INCREASED (HTTP 400), güncel birim fiyatın gönderdiğiniz expected_unit_price değerinin üzerinde olduğu anlamına gelir; oluşturma reddedilmiştir ve hiçbir tahsilat yapılmamıştır. Hata ayrıntıları hem expected_unit_price hem current_unit_price taşır ve eski rakamla körlemesine yeniden denemek her seferinde reddedilir. GET /catalog/subcategory/:itemId/price okuyun, yeni rakamın size hâlâ uyup uymadığına karar verin ve oluşturmayı güncellenmiş expected_unit_price ile ve aynı order_id ile yeniden gönderin — reddedilen deneme onu tüketmemiştir. expected_unit_price göndermemek korumayı tümüyle kapatır; otomatik bir entegrasyonun isteyeceği şey neredeyse hiç bu değildir.

Kısmen teslim edilen bir sipariş yanıtta nasıl görünür?

status partial olarak, ayrıntısı da sub_transaction_summary içinde görünür. Bu nesne birimleri completed, refunded veya pending olarak sayar — bu üçünün toplamı her zaman total değerine eşittir — ve sub_transactions her birimi kendi durumuyla listeler. Henüz teslim edilmemiş ya da iade edilmemiş bir birim pending olarak kalır. pending durumunu nihai saymayın ve beklemeye devam edin; completed, partial ve refunded nihai durumlardır. failed, processing ve retrying sayaçları yalnızca uyumluluk için korunur ve her zaman 0'dır. order.completed ve order.refunded çağrıları da aynı özeti taşır, dolayısıyla imzası doğrulanmış bir yük ikinci bir okuma olmadan kapatılabilir.

İzin listesinde olmayan bir adresten gelen istek ne alır?

İş mantığı çalışmadan önce dönen HTTP 403 ve IP_NOT_ALLOWED kodunu alır. Kontrol, anahtarın IP izin listesi boş olmadığı sürece geçerlidir; bu da sızmış bir anahtarı kendi çıkış adresleriniz dışında işe yaramaz hale getirir. Mesaj metnine değil error.code değerine göre dallanın ve üretimde aniden gelen bir IP_NOT_ALLOWED'ı bozuk anahtar değil, değişmiş bir çıkış adresi olarak okuyun — yeni bir NAT geçidi ya da yeni bir işçi alt ağı. İzin listesini yalnızca çıkış adresiniz gerçekten sabit değilse boş bırakın.

Bir siparişin tamamlanması ne kadar sürer?

Tamamlanmayı anında değil, asenkron kabul edin. POST /orders/create, sipariş kabul edilip cüzdandan tahsilat yapılır yapılmaz genellikle pending durumuyla yanıt döner; karşılama ardından ayrı işçilerde yürür ve nihai durum size webhook ile veya bir sonraki durum okumanızda ulaşır. Müşteriye dönük bir HTTP isteğini nihai durumu bekleyerek bloklamayın — siparişi kabul edin, müşterinize yanıt verin ve mutabakatı arka planda tamamlayın.

Hesap oluşturun, anahtar üretin, yayına geçin

Kaydolmakla API'yi çağırmak arasında ne onay kuyruğu ne de bekleme süresi var. Bayi hesabını oluşturun, API Access sayfasından bir anahtar üretin, cüzdanı doldurun; ilk siparişiniz aynı gün çıkabilir.

Bu projedeki geliştirici siz değil misiniz? Ticari taraf şurada anlatılıyor: SHOP2TOPUP bayi programı sayfası.

Bayiler için Oyun Yükleme ve Kupon API'si — REST API