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/accountHesap Bilgilerini GetirReferansı okuyun
GET/catalog/big-categoriesAna Kategorileri ListeleReferansı okuyun
GET/catalog/categoriesKategorileri ListeleReferansı okuyun
GET/catalog/subcategoriesAlt Kategorileri Listele (Ürünler)Referansı okuyun
GET/catalog/subcategory/:itemId/priceÜrün Fiyatını GetirReferansı okuyun
GET/catalog/category/:categoryId/requirementsKategori Gereksinimlerini GetirReferansı okuyun
POST/player/validateOyuncuyu DoğrulaReferansı okuyun
POST/orders/createSipariş OluşturReferansı okuyun
GET/orders/:orderIdSipariş Durumunu SorgulaReferansı okuyun
POST/orders/batchSiparişleri Toplu GetirReferansı okuyun
GET/ordersSiparişleri ListeleReferansı 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.completedSiparişin bir birimi teslim edildi. Siparişin genel durumu için data.status alanına bak.
order.refundedSiparişin bir birimi cüzdanına iade edildi. Tüm birimler iade edilince data.status değeri refunded olur.
webhook.testİşleyicini kontrol etmek için panelden imzalı bir test gönderimi başlattın.

Teslimat başlıkları

BaşlıkDeğer
Content-Typeapplication/json
X-Shop2Topup-Signaturesha256=<hex>: ham istek gövdesinin HMAC-SHA256 değeri
X-Shop2Topup-EventOlay adı (order.completed gibi)
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/* (beşi de)500060 snhesap başına
POST /orders/create500060 snhesap başına
GET /orders/:orderId360 snhesap + sipariş ID başına
POST /orders/batch260 snhesap başına
GET /orders260 snhesap başına
GET /account6060 snhesap başına
POST /player/validategünlük oyuncu kontrolü kotası24 saathesap başına (X-RateLimit başlıkları yok)

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_KEY401Geçersiz API anahtarıGönderilen API anahtarı sistemdeki aktif anahtarların hiçbiriyle eşleşmiyor.Anahtar ID'sinin ve gizli anahtarın doğru olduğunu kontrol et. Gerekirse API Erişimi panelinden anahtarı yeniden oluştur.action: FIX_INPUT · retryable: false · retry_after: null
IP_NOT_ALLOWED403IP adresi izin listesinde yokİstek, API anahtarının izin listesinde olmayan bir IP adresinden geldi.Sunucunun IP adresini API Erişimi panelinden izin listesine ekle ya da IP kısıtlamalarını kaldır.action: FIX_INPUT · retryable: false · retry_after: null
MISSING_REQUIRED_FIELD400Zorunlu alan eksikİstek gövdesinde zorunlu bir alan eksik. Oyuncu gereksinimlerinde eksik alanın adı details.field içinde yer alır.Endpoint dokümantasyonunu kontrol et. Oyuncu alanları için GET /catalog/category/:categoryId/requirements yanıtına da bak, ardından tüm zorunlu alanları gönder.action: FIX_INPUT · retryable: false · retry_after: null
INVALID_UUID_FORMAT400Geçersiz UUID formatıorder_id geçerli bir UUID formatında değil.Geçerli herhangi bir UUID biçimini kullanabilirsin (v1, v4, v7 vb.). Doğru yazım şöyle olmalı: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.action: FIX_INPUT · retryable: false · retry_after: null
PLAYER_NOT_FOUND400Oyuncu bulunamadıOyun, bu oyuncu ID'sinin mevcut olmadığını doğruladı. Bu kod yalnızca kesin bir yanıtta döner. ID kontrol edilemediğinde onun yerine PLAYER_CHECK_UNAVAILABLE alırsın.Oyuncu ID'sinin doğru olduğundan emin ol. Oyunun zone_id isteyip istemediğine de bak.action: FIX_INPUT · retryable: false · retry_after: null
PLAYER_CHECK_UNAVAILABLE503Bu oyunun oyuncu ID'leri şu an doğrulanamıyorBu oyun için oyuncu ID kontrolü şu anda yapılamıyor. Oyuncu ID'si REDDEDİLMEDİ.retry_after saniye sonra aynı isteği tekrar gönder. Müşterine ID'nin yanlış olduğunu söyleme.action: RETRY_LATER · retryable: true · retry_after: 60
REGION_MISMATCH400Bu oyuncunun bölgesi bu ürünü alamıyorOyuncu, bu ürünün teslim edilemediği bir bölgede. Hata yanıtında player_region ve player_name bilgileri de gelir.Oyuncunun bölgesine uygun ürünü seç.action: CHOOSE_OTHER · retryable: false · retry_after: null
INSUFFICIENT_BALANCE400Cüzdan bakiyesi yetersizCüzdanında bu siparişi tamamlamaya yetecek bakiye yok.Siparişi vermeden önce Yükleme sayfasından cüzdanına bakiye yükle.action: TOP_UP · retryable: false · retry_after: null
OUT_OF_STOCK400Ürün stokta yokİstediğin ürün şu anda stokta bulunmuyor.Daha sonra tekrar dene ya da başka bir ürün seç.action: RETRY_LATER · retryable: true · retry_after: null
PRODUCT_UNAVAILABLE409Bu ürün geçici olarak satışta değilÜrün mevcut ama şu anda satılamıyor.Daha sonra tekrar dene ya da başka bir ürün seç.action: RETRY_LATER · retryable: true · retry_after: null
DUPLICATE_ORDER409Yinelenen sipariş ID'siBu UUID ile oluşturulmuş bir sipariş zaten var. Bu bir idempotency korumasıdır.Yeni bir sipariş için yeni bir UUID oluştur. Bu hatayı bir yeniden denemede alıyorsan ilk deneme başarılı olmuş demektir: başka bir sipariş oluşturmak yerine o siparişi GET /orders/{orderId} ile oku.action: CHECK_ORDER · retryable: false · retry_after: null
PRICE_INCREASED400Fiyat beklenenin üzerine çıktıGüncel birim fiyat, gönderdiğin expected_unit_price değerinden yüksek.En güncel fiyatı GET /catalog/subcategory/:id/price ile çek ve expected_unit_price değerini güncelle.action: FIX_INPUT · retryable: false · retry_after: null
RATE_LIMIT_EXCEEDED429İstek limiti aşıldıBu endpoint için limiti aştın. Dakikalık kotalar (catalog, orders, account) her yanıtta X-RateLimit-Limit, X-RateLimit-Remaining ve X-RateLimit-Reset başlıklarını gönderir; details de limit, remaining (0), retry_after ve window_seconds değerlerini tekrarlar. POST /player/validate ise günlük oyuncu kontrol kotana göre ölçülür: X-RateLimit başlığı göndermez ve 429 yanıtında error içinde limit, current, remaining, reset_at ve reset_in_seconds yer alır. Her iki durumda da beklemen gereken saniye error.retry_after alanında ve Retry-After başlığında bulunur.Yeniden denemeden önce retry_after değerindeki ya da Retry-After başlığındaki saniye kadar bekle. Dakikalık endpoint'lerde limite hiç takılmamak için X-RateLimit-Remaining değerini takip et.action: RETRY_LATER · retryable: true · retry_after: sıfırlanmaya kalan saniye
INTERNAL_ERROR500Dahili sunucu hatasıSunucuda beklenmeyen bir hata oluştu.İsteği retry_after saniye sonra yeniden gönder. POST /orders/create için aynı order_id değerini kullan, böylece sipariş asla iki kez oluşmaz. Sorun devam ederse destekle iletişime geç.action: RETRY_LATER · retryable: true · retry_after: 30
Tam hata kodu referansını görün — 43 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ı.