Bu rehber, hazır bir entegratör yazılımı almak yerine kendi Trendyol satıcı entegrasyonunu yazan ya da mevcut entegrasyonundaki hataları ayıklayan geliştiriciler ve şirket içi ekipler için hazırlandı. Amaç; ERP'nizi, depo yönetim sisteminizi (WMS) ya da e-ticaret sitenizi Trendyol Pazaryeri (Partner) API'sine bağlayıp ürün, stok-fiyat, sipariş, kargo etiketi, fatura ve iade akışlarını otomatikleştirmek. Anlatılanlar, developers.trendyol.com adresindeki resmî dokümantasyonun Ekim 2026 sonundaki hâline dayanıyor. Eski kodlara ya da blog yazılarına bakıyorsanız iki değişikliği bilmeniz gerekir: Dokümantasyon, Ürün V1 servisleri ve eski /orders sipariş listeleme uç noktası (endpoint) için 15 Ekim 2026'yı son tarih olarak belirledi; yeni kod yalnızca Ürün V2 ve /v2/orders kullanmalı. Ayrıca 14 Eylül 2026'dan bu yana ürün servislerindeki istek sınırları (rate limit) uç nokta bazında değil, servis grubu bazında ve listeleme limitinize göre uygulanıyor. Pazaryerinde satışın günlük operasyon tarafını Pazaryerlerinde satış: operasyonel taraf konusunda ele aldık; bu yazı API'ye odaklanıyor.
Kendi entegrasyonunuz mu, entegratör mü, dosya yükleme mi?
| Seçenek | Ne zaman uygun? | Dikkat edilecekler |
|---|---|---|
| Kendi entegrasyonunuz (User-Agent: "{sellerId} - SelfIntegration") | Stok ve fiyat zaten bir ERP veya WMS'de tutuluyorsa, kodu sürdürecek bir geliştirici varsa ve hazır araçların karşılamadığı akışlarınız (çoklu depo, set ürün, kendi fatura hattınız) bulunuyorsa | Trendyol kod desteği vermediğini açıkça belirtiyor; API değişikliklerini sizin izlemeniz gerekir |
| Entegratör yazılımı | Birden çok pazaryeri, yazılım ekibi olmaması, hızlı başlangıç ihtiyacı | Abonelik ya da kullanım ücreti, entegratörün veri modeli, Trendyol bir şeyi değiştirdiğinde entegratörün güncellemesini beklemek |
| Satıcı Paneli'nin toplu işlem araçları veya XML/Excel tabanlı servisler | Küçük katalog, az sipariş, seyrek değişen stok | Elle yapılan adımlar, yavaş stok güncellemesi, kampanyalarda aşırı satış (overselling) riski |
Mimari: tek doğruluk kaynağı, kuyruklar ve idempotent işleme
Trendyol'u bir satış kanalı olarak görün, asla ana veri kaynağı olarak değil. İçerik, stok ve fiyat ERP'nizde, PIM'inizde ya da kendi veritabanınızda durur; entegrasyon değişiklikleri dışarı yayınlar, siparişleri, iadeleri ve hakedişleri içeri çeker.
Kod:
ERP / WMS / PIM (ana veri: ürün, stok, fiyat)
| değişiklik günlüğü veya outbox: yalnızca değişen kayıtlar
v
kuyruk ---> katalog işçisi ---> Ürün V2 uç noktaları ---> batchRequestId
---> stok işçisi ---> price-and-inventory ---> batchRequestId
batch takipçisi: toplu işlem sonucunu sorgular, FAILED kalemleri göreve çevirir
Trendyol webhook ---> alıcı (kimlik kontrolü, ham olayı sakla, 200 OK) ---> kuyruk
mutabakat işi (birkaç dakikada bir orders/stream) ---------------------> kuyruk
kuyruk ---> sipariş işçisi (idempotent: shipmentPackageId + status + lastModifiedDate)
---> ERP / WMS: stok ayır, topla, faturala, etiketle, gönder
- Eşleme tabloları (mapping). Kendi stok kodunuzun yanında barcode (stok, fiyat ve siparişte anahtar), stockCode, productMainId (varyantları gruplayan "model kodu"), contentId, categoryId, brandId, özellik ve değer ID'leri, adres ID'leri ve kargo firması kodları dursun. Reddedilen toplu isteklerin çoğu eksik ya da bayatlamış bir eşlemeden çıkar.
- Uç nokta başına değil, limit grubu başına bir hız sınırlayıcı. Ürün yazma, ürün okuma ve stok-fiyat güncelleme servisleri kendi içlerinde tek bir bütçeyi paylaşır (ayrıntısı aşağıda).
- İdempotent tüketiciler. Webhook'lar yeniden dener, mutabakat işi aynı paketleri tekrar okur, işçiler yarıda çöker; her işleyici aynı olayı iki kez işlediğinde de aynı sonucu vermeli.
- Her yazma işlemi doğrulamayla biter. Ürün ve stok-fiyat çağrıları sonuç değil, batchRequestId döndürür.
- Sır içermeyen loglar. Gövdeleri batchRequestId veya shipmentPackageId ile saklayın ama Authorization başlığını maskeleyin. Sipariş verisinde ad, adres, telefon ve bazen T.C. kimlik numarası bulunur; KVKK (6698 sayılı Kanun) gereği erişimi sınırlayın, veriyi şifreli saklayın, bir saklama süresi belirleyin.
- İçeride UTC, dönüşüm kenarda. Zaman damgaları milisaniye cinsinden epoch değerleridir; bazıları GMT+3, bazıları GMT olarak belgelenmiş.
İstek bütçesini hesaplamak
Kayıt değil, çağrı sayın. Varsayımlar: en düşük listeleme limiti kademesi (50.000 ürün), 20.000 aktif barkod, iki haftada yaklaşık 3.000 paket değişikliği ve karar bekleyen 60 iade.| İş | Çağrı | Sınırlayan ne? |
|---|---|---|
| 20.000 barkodun tam stok-fiyat gönderimi | 1.000'er kalemlik 20 istek | Inventory & Price Write, bu kademede dakikada 350: darboğaz değil |
| 5.000 ürün oluşturma | 5 istek ve batch sorguları | Veri kalitesi ve onay süresi |
| Son 14 günde değişen paketleri stream ile yeniden okuma | size=200 ile yaklaşık 15 istek | Çağrılar arasında önerilen 5 saniye: yaklaşık 75 saniye |
| 60 iadeyi tek tek onaylama | 60 istek | Dakikada 5: yaklaşık 12 dakika |
Kimlik doğrulama, başlıklar ve test ortamı
- Kimlik bilgileri: Satıcı ID, API Key ve API Secret, Satıcı Paneli'nde Hesap Bilgilerim > Entegrasyon Bilgileri sayfasındadır ve yalnızca ana kullanıcıya (admin rolü) görünür. Aynı sayfadaki "Entegrasyon Referans Kodu" destek taleplerinde istenir; API anahtarı kadar gizli tutun.
- Basic Authentication: Kullanıcı adı olarak API Key, parola olarak API Secret kullanılır. Satıcı ID URL yolunda yer alır.
- User-Agent zorunludur: Kendi yazılımınız için "{sellerId} - SelfIntegration", entegratör için "{sellerId} - {EntegratörAdı}" (alfanümerik, en fazla 30 karakter). User-Agent göndermeyen istekler 403 ile engellenir.
- Canlı ortam: https://apigw.trendyol.com; IP yetkilendirmesi gerekmez, ancak Trendyol bir IP'nin çeşitli nedenlerle engellenebileceğini belirtiyor.
- Test ortamı (stage): https://stageapigw.trendyol.com; ayrı hesap (panel: stagepartner.trendyol.com) ve ayrı anahtarlar. Stage yalnızca yetkilendirilmiş IPv4 çıkış adreslerine yanıt verir, aksi hâlde 503 döner. Yetkilendirmeyi satıcı paneli üzerinden destek talebiyle isteyebilirsiniz (doküman IP'nin 24 saat içinde tanımlandığını söylüyor) ya da Test Ortam IP Whitelist Yönetimi servisleriyle kendiniz yönetebilirsiniz. Bu servisler canlı ağ geçidinde, canlı satıcı ID'nizle çağrılır. Sınırlar: en fazla 7 IPv4 adresi ve günde en fazla 7 ekleme; her kaydın bir expiresAt (son geçerlilik) tarihi vardır.
Bash:
export TY_BASE="https://apigw.trendyol.com" # test: https://stageapigw.trendyol.com
export TY_SELLER_ID="123456" # yer tutucu
export TY_API_KEY="..." # sır kasasından okuyun, asla git'ten değil
export TY_API_SECRET="..."
curl -sS "$TY_BASE/integration/product/sellers/$TY_SELLER_ID/products/approved?page=0&size=1" \
-u "$TY_API_KEY:$TY_API_SECRET" \
-H "User-Agent: $TY_SELLER_ID - SelfIntegration" \
-H "Accept: application/json"
YAML:
trendyol:
prod:
base_url: https://apigw.trendyol.com
seller_id: ${TY_PROD_SELLER_ID}
api_key: ${TY_PROD_API_KEY}
api_secret: ${TY_PROD_API_SECRET}
stage:
base_url: https://stageapigw.trendyol.com
seller_id: ${TY_STAGE_SELLER_ID} # test hesabı canlıdakinden farklıdır
api_key: ${TY_STAGE_API_KEY}
api_secret: ${TY_STAGE_API_SECRET}
integrator_name: SelfIntegration # User-Agent: "{seller_id} - SelfIntegration"
limits_per_minute: { product_read: 1000, product_write: 200, inventory_price_write: 350, order_list: 30, claim_approve: 5 } # en düşük kademe
14 Eylül 2026'dan itibaren istek sınırları
Servis Limitleri sayfası sınırları listeleme limiti kademenize (50.000, 75.000, 150.000 veya 500.000 ürün ya da limitsiz) bağlıyor:| Grup | Kapsam | Dakikada istek, en düşük kademeden limitsize |
|---|---|---|
| Product Integration Read | Ürün filtreleme, toplu işlem kontrolü, marka, kategori, özellik ve değer listeleri, adresler, güncelleme sonucu kontrolü | 1.000 - 2.000 |
| Product Integration Write | Ürün yaratma, güncelleme, silme, arşivleme, kilit kaldırma, marka yaratma, buybox kontrolü | 200 - 600 |
| Inventory & Price Write | Stok ve fiyat güncelleme | 350 - 2.000 |
| Sipariş paketleri | Sipariş Paketlerini Çekme | 30 - 100 |
| İadeler | İade onaylama; ret talebi oluşturma | Her biri 5 |
Uç nokta haritası
Yollar, ortamın temel adresine eklenir.| İş (dokümandaki servis adı) | Metot ve yol |
|---|---|
| Kategori ağacı (getCategoryTree) | GET /integration/product/product-categories |
| Kategori özellik listesi v2, özellik değerleri v2 | GET /integration/product/categories/{categoryId}/attributes, .../attributes/{attributeId}/values |
| Marka listesi, marka yaratma | GET /integration/product/brands, POST /integration/product/sellers/{sellerId}/brands |
| İade ve sevkiyat adresleri | GET /integration/sellers/{sellerId}/addresses |
| Ürün yaratma v2 | POST /integration/product/sellers/{sellerId}/v2/products |
| Toplu işlem kontrolü (getBatchRequestResult) | GET /integration/product/sellers/{sellerId}/products/batch-requests/{batchRequestId} |
| Onaylı ve onaysız ürün filtreleme v2 | GET .../sellers/{sellerId}/products/approved, .../products/unapproved |
| Stok ve fiyat güncelleme (updatePriceAndInventory) | POST /integration/inventory/sellers/{sellerId}/products/price-and-inventory |
| Sipariş paketlerini çekme (getShipmentPackages) | GET /integration/order/sellers/{sellerId}/v2/orders |
| Akış ile çekme (getShipmentPackagesStream) | GET /integration/order/sellers/{sellerId}/orders/stream |
| Paket statü bildirimi (updatePackage) | PUT /integration/order/sellers/{sellerId}/shipment-packages/{packageId} |
| Fatura linki, fatura dosyası | POST /integration/sellers/{sellerId}/seller-invoice-links, .../seller-invoice-file |
| Ortak etiket (createCommonLabel, getCommonLabel) | POST, GET /integration/sellers/{sellerId}/common-label/{cargoTrackingNumber} |
| Webhook | /integration/webhook/sellers/{sellerId}/webhooks |
| İadeler (getClaims) | GET /integration/order/sellers/{sellerId}/claims |
Katalog: kategori, özellik ve ürün yaratma
Ürün yaratma (V1'deki adıyla ürün aktarma) asenkron çalışır ve referans verilerde (kategori, özellik, marka) hata kabul etmez; bu yüzden önce sözlükleri kurun:- Kategoriler: Yalnızca en alt seviyedeki (yaprak) kategoriler ürün kabul eder. Doküman kategori ağacını ve özellikleri haftalık yenilemenizi öneriyor.
- Özellikler (attribute): Her kategori için required (zorunlu), allowCustom (ID yerine customAttributeValue ile serbest metin gönderilir), allowMultipleAttributeValues (birden fazla değer), slicer (ayrı ürün kartı açar, genelde renk) ve varianter (aynı kart üzerindeki varyant, genelde beden; kategori başına bir tane) bilgisi gelir.
- Değerler, markalar, adresler: Değer servisini sayfalayarak (size en fazla 1.000) çekin; brandId'yi marka listesinden alın ya da markayı yaratın; shipmentAddressId ve returningAddressId'yi adres servisinden okuyun.
JSON:
{
"items": [
{
"barcode": "DEMO-USBC-1M-BLK",
"title": "USB-C - USB-C Şarj Kablosu 1 m 60 W Siyah",
"productMainId": "DEMO-USBC-1M",
"brandId": 1234567,
"categoryId": 7654,
"quantity": 40,
"stockCode": "USBC-1M-BLK",
"dimensionalWeight": 1,
"description": "Örgü kaplı USB-C kablo, 1 m, 60 W şarj desteği.",
"listPrice": 299.90,
"salePrice": 249.90,
"vatRate": 20,
"images": [{ "url": "https://cdn.example.com/usbc-1m-blk-1.jpg" }],
"attributes": [
{ "attributeId": 47, "customAttributeValue": "Siyah" },
{ "attributeId": 1111, "attributeValueId": 22222 }
],
"shipmentAddressId": 111,
"returningAddressId": 222
}
]
}
Toplu işlem sonucunu (batch result) sorgulamak
Yaratma yanıtı yalnızca bir batchRequestId'dir; Trendyol'un Sıkça Sorulan Sorular sayfası da bunun işlemin başarılı olduğu anlamına gelmediğini açıkça yazıyor. Toplu işlem COMPLETED olana kadar sorgulayın. Stok-fiyat işlemlerinde batch düzeyinde status dönmez; orada her kalemin SUCCESS ya da FAILED olduğuna bakılır. Sonuçlar 4 saat boyunca sorgulanabilir.
Python:
import time
import requests
def make_session(seller_id, api_key, api_secret):
s = requests.Session()
s.auth = (api_key, api_secret)
s.headers.update({"User-Agent": f"{seller_id} - SelfIntegration",
"Accept": "application/json"})
return s
def wait_for_batch(s, base, seller_id, batch_id, timeout_s=1800):
url = f"{base}/integration/product/sellers/{seller_id}/products/batch-requests/{batch_id}"
delay, deadline = 5, time.monotonic() + timeout_s
while time.monotonic() < deadline:
r = s.get(url, timeout=30)
if r.status_code != 429:
r.raise_for_status()
body = r.json()
items = body.get("items") or []
done = body.get("status") == "COMPLETED" or (
items and all(it.get("status") in ("SUCCESS", "FAILED") for it in items))
if done:
return [(it.get("requestItem", {}).get("barcode"), it.get("status"),
it.get("failureReasons", [])) for it in items]
time.sleep(delay)
delay = min(delay * 2, 120)
raise TimeoutError(f"batch {batch_id} hâlâ işleniyor")
Onay, güncelleme, arşivleme ve silme
- Başarılı kalemler içerik onayına girer. Onaysız ürün filtresi pendingApproval (onay bekliyor) ya da rejected (reddedildi) gösterir; reddedilen ürünü düzeltip unapproved-bulk-update ile yeniden gönderirsiniz. Onaylı ürünler onSale, notOnSale, archived, locked veya blacklisted statüsündedir. Sayfalama page × size = 10.000'de durur; ötesi için yanıttaki nextPageToken kullanılır.
- Onaylı ürünler content-bulk-update, variant-bulk-update (stockCode, KDV oranı, adresler, desi) ve delivery-info-bulk-update ile güncellenir. Barcode, productMainId, brandId, categoryId ve slicer/varianter değerleri değiştirilemez. İki tuzak var: Tek bir özelliği değiştirecekseniz tüm özellikleri göndermeniz gerekir ve null gönderilen alan üründen silinir. İçerik kontrolünün sonucu contentId bazında update-audits uç noktasından okunur.
- Arşivleme (archive-state) ürünü silmeden gizler. Yalnızca onay bekleyen ürünler ile bir günden uzun süredir arşivde duran ve Trendyol tarafından satışı durdurulmamış onaylı ürünler silinebilir. Çok düşük ya da çok yüksek fiyat, kritik fiyat hatası veya tedarik edememe yüzünden kilitlenen ürünlerin kilidi, sebep giderildikten sonra kilit kaldırma servisiyle açılır.
Stok ve fiyat senkronizasyonu (updatePriceAndInventory)
POST /integration/inventory/sellers/{sellerId}/products/price-and-inventory istek başına en fazla 1.000 barkod alır; bir kalemde yalnızca quantity ya da yalnızca fiyatlar olabilir. quantity satılabilir stoktur: Trendyol sipariş geldikçe düşürür, sizin bir sonraki değerinizle değiştirir; doküman ürün başına üst sınırı 20.000 adet olarak veriyor. listPrice üstü çizili fiyat (PSF), salePrice satış fiyatıdır (TSF).
JSON:
{
"items": [
{ "barcode": "DEMO-USBC-1M-BLK", "quantity": 37, "listPrice": 299.90, "salePrice": 249.90 },
{ "barcode": "DEMO-USBC-1M-WHT", "quantity": 0 },
{ "barcode": "DEMO-USBC-2M-BLK", "listPrice": 329.90, "salePrice": 289.90 }
]
}
- Fark (delta) gönderin. Aynı gövde 15 dakika içinde tekrar gelirse "15 dakika boyunca aynı isteği tekrarlı olarak atamazsınız!" hatası döner. Yalnızca son kabul edilen gönderimden bu yana değişen barkodları yollayın.
- Fiyat güncellemelerini birleştirin. Aynı barkod için dakikada 30'dan fazla fiyat güncellemesi hata verir; her döngüde barkod başına yalnızca son fiyatı tutun.
- 429 ve 5xx'te geri çekilin. Rastgele sapmalı (jitter) üstel bekleme (backoff) kullanın ve deneme sayısına üst sınır koyun. Diğer 4xx yanıtları veri sorunudur: düzeltin, yeniden denemeyin.
- Zaman aşımından sonra önce doğrulayın. Aynı gövdeyi hemen yeniden göndermek 15 dakika kuralına takılabilir; bu hata zaten ilk isteğin ulaştığını gösterir. Körlemesine yeniden göndermek yerine batch sonucuna ya da stok-fiyat filtresine bakın.
- Her gün mutabakat yapın: Onaylı ürün stok ve fiyat filtresiyle (size en fazla 100, 10.000'den sonrası nextPageToken ile) Trendyol'daki değerleri çekip ana verinizle karşılaştırın, farkları gönderin.
Python:
import hashlib, json, random, time
def push_deltas(s, base, seller_id, changes, recently_sent):
"""changes: barcode ile birlikte quantity ve/veya fiyat içeren sözlükler.
recently_sent: özet -> unix zamanı, çalıştırmalar arasında saklanır."""
url = f"{base}/integration/inventory/sellers/{seller_id}/products/price-and-inventory"
batch_ids = []
for start in range(0, len(changes), 1000):
body = {"items": changes[start:start + 1000]}
digest = hashlib.sha256(json.dumps(body, sort_keys=True).encode()).hexdigest()
if time.time() - recently_sent.get(digest, 0) < 15 * 60:
continue # aynı gövde 15 dakikadan kısa süre önce kabul edildi
for attempt in range(6):
r = s.post(url, json=body, timeout=30)
if r.status_code == 429 or r.status_code >= 500:
time.sleep(min(60, 2 ** attempt) + random.random())
continue
r.raise_for_status() # diğer 4xx: veriyi düzeltin, yeniden denemeyin
batch_ids.append(r.json()["batchRequestId"])
recently_sent[digest] = time.time()
break
return batch_ids
Siparişler: listeleme, statüler, paketler, fatura ve etiket
| Konu | /v2/orders (getShipmentPackages) | /orders/stream (getShipmentPackagesStream) |
|---|---|---|
| Ne için? | Hedefli sorgular: bir orderNumber, paket ID'leri, bir statü, yakın tarihli bir aralık | Periyodik senkronizasyon, kesinti sonrası açığı kapatma, dışa aktarım |
| Sayfalama | page ve size (en fazla 200); sorgu başına en fazla 10.000 paket (size=200 ile 0-49. sayfalar) | Opak nextCursor ve hasMore; imleç (cursor) kullanılırken filtre değişirse 400 döner |
| Zaman aralığı | Varsayılan son bir hafta, sorgu başına en fazla iki hafta, son bir ayın verisi | lastModifiedStartDate/EndDate ile sorgu başına en fazla 14 gün, son 3 ayın verisi |
| Sıralama ve hız | orderByField=PackageLastModifiedDate, ASC veya DESC; kademeye göre dakikada 30-100 istek | Sabit: lastModifiedDate azalan; istekler arasında en az 5 saniye önerilir |
Python:
def changed_packages(s, base, seller_id, since_ms, until_ms):
url = f"{base}/integration/order/sellers/{seller_id}/orders/stream"
params = {"lastModifiedStartDate": since_ms, "lastModifiedEndDate": until_ms, "size": 200}
while True:
r = s.get(url, params=params, timeout=60)
if r.status_code == 429:
time.sleep(30)
continue
r.raise_for_status()
page = r.json()
yield from page.get("content", [])
if not page.get("hasMore"):
return
params["nextCursor"] = page["nextCursor"] # opak değer: olduğu gibi geri gönderin
time.sleep(5)
| Paket statüsü | Anlamı ve yapılacak iş |
|---|---|
| Awaiting, Verified | Ödeme kontrolü sürüyor: stoğu ayırın, Created olana kadar başka işlem yapmayın |
| Created | Gönderime hazır: hazırlığa başlayın |
| Picking, Invoiced | Sizin bildirdiğiniz statüler, bu sırayla |
| Shipped, AtCollectionPoint | Kargoda ya da teslim noktasında bekliyor |
| Delivered | Son statü |
| UnDelivered, Returned | Teslim edilemedi; Returned (son statü) paketin size geri döndüğünü gösterir |
| Cancelled, UnSupplied | İptal, tedarik edememe dâhil: stok ayırmasını bırakın |
| UnPacked | Paket bölündü; yerini yeni paketler aldı |
Picking, Invoiced, tedarik edememe ve paket bölme
Paket statü bildirimi PUT /integration/order/sellers/{sellerId}/shipment-packages/{packageId} adresine yapılır; önce Picking, sonra Invoiced:
JSON:
{
"lines": [{ "lineId": 4765111111, "quantity": 1 }],
"params": { "invoiceNumber": "ABC2026000000123" },
"status": "Invoiced"
}
Fatura
Faturanın kendisi e-Fatura ya da e-Arşiv altyapınızdan çıkar; Trendyol'a ise faturanın PDF dosyasını siparişteki kriptolu e-posta adresine gönderirsiniz ya da iki çağrıdan birini kullanırsınız:- Fatura linki gönderme (sendInvoiceLink): POST .../seller-invoice-links; gövdede invoiceLink, shipmentPackageId, invoiceDateTime (10 haneli saniye ya da 13 haneli milisaniye) ve invoiceNumber bulunur, başarılı yanıt 201'dir. Numara ve tarih mikro ihracat ile Trendyol Yurt Dışı Aracılığı paketlerinde zorunludur; fatura numarası üç alfanümerik karakter, bir yıl ve dokuz rakamdan oluşan 16 karakterlik kalıba uymalıdır. Doküman, linkin yasal zorunluluk gereği 10 yıl erişilebilir kalmasını istiyor. 409, paketin zaten faturalandığını ya da linkin başka bir pakete gönderildiğini gösterir.
- Fatura dosyası gönderme: POST .../seller-invoice-file; multipart form-data ile shipmentPackageId ve file (PDF, JPEG veya PNG, en fazla 10 MB, ileri tarihli olamaz).
Etiket ve kargo firmaları
Trendyol'un ödediği Trendyol Express (TEX) ya da Aras Kargo gönderilerinde ortak etiket isteyin: POST .../common-label/{cargoTrackingNumber} çağrısına {"format": "ZPL", "boxQuantity": 2} gibi bir gövde gönderin, ardından aynı yola GET isteğiyle koli başına bir ZPL etiketi alın. Doküman bunu Picking ya da Invoiced bildiriminden sonra yapmanızı öneriyor. Tek format ZPL olduğu için bu dili anlayan bir termal etiket yazıcısı gerekir. Siparişte whoPays = 1 geliyorsa gönderi kendi kargo anlaşmanızla yapılır ve etiket o kargo firmasından alınır. cargoTrackingNumber, Satıcı Paneli etiketlerinde kullanılan CODE128 barkodudur. Kargo firmasını değiştirmek için PUT .../shipment-packages/{packageId}/cargo-providers çağrısına YKMP, ARASMP veya TEXMP gibi bir kod gönderin; bu işlem bir paket için 5 dakikada bir kez yapılabilir ve TEX kotanız doluysa TEX'e geçiş isteği geçersiz olur.Sipariş paketleri için webhook
Webhook, paketin tamamını getShipmentPackages modeliyle, abone olduğunuz statülerden birine geçtiğinde adresinize gönderir: CREATED, PICKING, INVOICED, SHIPPED, CANCELLED, DELIVERED, UNDELIVERED, RETURNED, UNSUPPLIED, AWAITING, UNPACKED, AT_COLLECTION_POINT veya VERIFIED.- Yaratma: POST /integration/webhook/sellers/{sellerId}/webhooks; gövdede url, authenticationType (username ve password ile BASIC_AUTHENTICATION ya da x-api-key başlığında gelen apiKey ile API_KEY) ve subscribedStatuses. Boş liste, sonradan eklenecekler dâhil tüm statülere abone olmak demektir. GET ile listelenir; .../webhooks/{id} üzerinde PUT güncelleme, DELETE silme yapar; .../activate ve .../deactivate aktife ve pasife alır.
- Sınırlar: Satıcı başına pasifler dâhil en fazla 15 webhook; URL "Trendyol", "Dolap" veya "localhost" ifadesi içeremez.
- Yeniden deneme: Başarısız gönderimler başarılı olana kadar 5 dakikada bir tekrarlanır (doküman bunun ileride değişeceğini söylüyor). SSS'ye göre 13 hatadan sonra webhook pasife alınır ve iki e-posta gönderilir; alıcıyı düzeltip yeniden aktife alırsınız.
Python:
import hmac, json, os
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_KEY = os.environ["TY_WEBHOOK_KEY"] # webhook'u yaratırken verdiğiniz apiKey
@app.post("/hooks/trendyol/packages")
def trendyol_packages():
if not hmac.compare_digest(request.headers.get("x-api-key", ""), WEBHOOK_KEY):
abort(401)
body = request.get_json(silent=True) or {}
# Doküman sayfalı getShipmentPackages zarfını gösteriyor; tek paketi de kabul edin.
packages = body["content"] if isinstance(body.get("content"), list) else [body]
for pkg in packages:
# tekil anahtar (shipmentPackageId, status, lastModifiedDate), INSERT ... ON CONFLICT DO NOTHING
save_event_once(pkg["shipmentPackageId"], pkg.get("status"),
pkg.get("lastModifiedDate") or 0, json.dumps(pkg))
return "ok", 200
İadeler (claims)
- İadesi oluşturulan siparişleri çekme (getClaims): claimItemStatus (Created, WaitingInAction, Accepted, Cancelled, Rejected, Unresolved, InAnalysis), lastModifiedDate üzerinde startDate/endDate ya da claimIds ile filtreleyin; yeni iadeler için Created sorgulayın. claimDate GMT'dir, Satıcı Paneli ile eşlemek için üç saat ekleyin.
- İade onaylama (approveClaimLineItems): PUT .../claims/{claimId}/items/approve ve claimLineItemIdList; yalnızca WaitingInAction statüsünde. Bazı onaylar Accepted'e geçmeden önce WaitingFraudCheck statüsünden geçer.
- Ret talebi (createClaimIssue): Sebebi GET /integration/order/claim-issue-reasons ile alın, ardından POST .../claims/{claimId}/issue çağrısına claimIssueReasonId, claimItemIdList ve en fazla 500 karakterlik description'ı sorgu parametresi, kanıtları ise multipart dosya (her biri en fazla 5 MB) olarak ekleyin. Dosya, 1651 (iade paketi elime ulaşmadı) gibi birkaç sebep dışında zorunludur; 1651, WaitingInAction'ın ilk 24 saatinde seçilemez.
- Sebep listesini koda gömmeyin: 8 Ekim 2026'da Trendyol bazı sebepleri (251 ve 1701 dâhil) kaldırdı, 2077, 2078 ve 2079'u ekledi. Listeyi servisten yükleyin.
- İki işlemi de kuyruğa alın: Onay ve ret talebi servisleri dakikada 5 istekle sınırlı.
Müşteri soruları ve finans, kısaca
Sorular: GET /integration/qna/sellers/{sellerId}/questions/filter?status=WAITING_FOR_ANSWER (tarih vermezseniz son bir hafta, sorgu başına en fazla iki hafta, size en fazla 50). Yanıt POST .../questions/{id}/answers ve {"text": "..."} ile verilir; metin 10-2.000 karakter olmalı ve yayımlanmadan önce yasaklı kelime kontrolünden geçer.Finans: /integration/finance/che/sellers/{sellerId}/ altındaki Cari Hesap Ekstresi servisleri settlements (satış, iade, indirim, kupon, komisyon düzeltmeleri; transactionType veya transactionTypes zorunlu), otherfinancials (hakediş ödemeleri, virman, faturalar, kesintiler) ve payment-order (ödenmiş ödeme emirleri, sayfa başına 10) verir. En fazla 15 günlük tarih aralığıyla, paymentDate ile ya da bir banka ödemesini kapsadığı siparişlerle eşlemenin anahtarı olan paymentOrderId ile filtreleyin. Kayıtlar teslimattan sonra oluşur; doküman, ödeme emirlerinin istisnalar dışında her çarşamba o hafta vadesi gelen siparişler için oluştuğunu söylüyor. Sınır: dakikada 100 istek.
Test ve canlıya geçiş kontrol listesi
Test ortamında POST /integration/test/order/orders/core bir test siparişi yaratır (test satıcı ID'si SellerID başlığında, kimlik doğrulama aynı satıcıyla; kurumsal faturalı sipariş için commercial, mikro ihracat için microRegion). PUT /integration/test/order/sellers/{sellerId}/shipment-packages/{packageId}/status ise siparişi Shipped, AtCollectionPoint, Delivered, UnDelivered veya Returned statüsüne yalnızca ileri yönde taşır. Yalnızca stage'de çalışan iki servis daha test iadesini WaitingInAction'a çeker ve müşteri sorusu oluşturur.- Her istekte User-Agent var; anahtarlar sır kasasından geliyor; canlı ve test ayarları karışamıyor.
- Yalnızca yaprak kategoriler kullanılıyor; özellik önbellekleri haftalık yenileniyor; her stok kodu barcode, productMainId ve contentId ile eşlenmiş.
- Her yazma işlemi batch sonucuna kadar izleniyor; FAILED kalemler bir insana ulaşıyor.
- Stok-fiyat gönderimleri delta; 15 dakika içinde aynı gövde gitmiyor; her limit grubu için bir sınırlayıcı var; 429 alındığında bekleme (backoff) uygulanıyor.
- Stok tamponu tanımlı; Awaiting siparişler stok ayırıyor ama gönderilmiyor.
- Siparişler webhook ve stream mutabakatıyla geliyor; işleme idempotent; yerine yenisi gelen paketler (iptal, bölme) ele alınıyor.
- Picking, Invoiced, fatura linki ya da dosyası, etiket ve gönderim stage'de uçtan uca, 409 durumu dâhil denendi.
- İadeler dakikada 5 isteğe uyan bir kuyruktan geçiyor; sebepler servisten yükleniyor.
- 401, 403, 429 ve 5xx artışlarında, FAILED kalemlerde ve "webhook pasife alındı" e-postasında alarm çalıyor.
- Changelog'un ve API durum sayfasının (dokümandaki Api HealthCheck) bir sahibi var.
Sorun giderme
| Belirti | Olası neden | Çözüm |
|---|---|---|
| 401 ClientApiAuthenticationException | Yanlış anahtar ya da secret, canlıda test anahtarı (veya tersi), anahtar ve secret yer değiştirmiş | Ortamı kontrol edin; kullanıcı adı = API Key, parola = API Secret |
| 403 | User-Agent eksik; hata kodları sayfası yanlış uç nokta ya da parametreyi de sayıyor | Her istekte "{sellerId} - SelfIntegration" gönderin; yolu dokümanla karşılaştırın |
| Yalnızca stage'de 503 | Çıkış IP'niz yetkilendirilmemiş ya da kaydın süresi dolmuş | IPv4 adresini talep ya da whitelist servisiyle ekleyin; expiresAt'i izleyin |
| Yurt dışındaki sunucudan canlıda zaman aşımı | İngilizce dokümanın yurt dışı internet sağlayıcıları için andığı durum | IP adresinizle Partner Support üzerinden talep açın |
| 429 too.many.requests | Grup sınırı ya da tek uç noktada 10 saniyede 50 istek | Grup başına sınırlayıcı, geri çekilme, daha az ve daha dolu batch |
| 426 ya da "scheduled brownout" mesajı | Kullanımdan kalkan uç noktalar (Ürün V1, eski /orders) | Ürün V2 ve /v2/orders'a geçin |
| "15 dakika boyunca aynı isteği..." | 15 dakika içinde aynı stok-fiyat gövdesi | Delta gönderin; önceki batch'i doğrulayın |
| Batch kalemi FAILED | Eksik ya da geçersiz özellik, yaprak olmayan kategori, görsel adresi, salePrice'tan düşük listPrice, hatalı barkod | failureReasons'ı okuyun, sözlükleri yenileyin, yeniden gönderin |
| /v2/orders'ta eksik sipariş | İki haftalık ya da bir aylık pencerenin dışında veya 10.000 paketin ötesinde | Stream'i ya da daha dar aralıkları kullanın |
| Saatler üç saat kaymış | orderDate ve sorgu tarihleri GMT+3, packageHistories.createdDate ve claimDate GMT olarak belgelenmiş | Girişte UTC'ye çevirin; saati bilinen bir stage siparişiyle bir kez doğrulayın |
| Paket ID'si artık geçersiz | Kısmi iptal ya da bölme paketi değiştirdi | orderNumber ile yeniden okuyun; originPackageIds'i izleyin |
| Fatura linkinde 409 | Paket zaten faturalı ya da link başka pakette kullanılmış | invoiceStatus'a bakın; her pakete tekil bir link |
| Webhook pasife düştü | Art arda başarısız gönderimler | Alıcıyı düzeltin, .../activate çağırın, aradaki boşluğu stream ile okuyun |
Sıkça sorulan sorular
Trendyol'a bağlanmak için entegratör şart mı?Hayır. Her satıcı kendi anahtarları ve "SelfIntegration" User-Agent'ıyla API'yi çağırabilir. Yazılım ekibi olmadan birden çok pazaryerinde satıyorsanız entegratör mantıklıdır. Her iki durumda da Trendyol kod desteği vermediğini belirtiyor.
Trendyol test ortamı (sandbox) var mı?
Var: stageapigw.trendyol.com adresindeki stage ortamı; ayrı hesabı, ayrı anahtarları ve IP yetkilendirmesi var. Orada test siparişi yaratabilir, teslimat statülerinde ilerletebilir, test iadesini WaitingInAction'a çekebilir ve müşteri sorusu oluşturabilirsiniz.
Stoğu ne sıklıkla senkronize etmeliyim?
Değişiklikleri oldukları anda küçük delta paketleriyle gönderin, günde bir kez de tam mutabakat yapın. Trendyol sabit bir aralık vermiyor; pratik sınırlar grup istek sınırı, 15 dakika kuralı ve barkod başına dakikada 30 fiyat güncellemesi.
Siparişleri yalnızca webhook ile alabilir miyim?
Önerilmez. Trendyol webhook'un yanında periyodik senkronizasyonu öneriyor ve 2026'da changelog'da kesintiler sonrasında satıcılardan birden fazla kez sipariş aralıklarını yeniden sorgulamalarını istedi.
Trendyol Express etiketleri nasıl alınır?
Trendyol'un ödediği TEX ya da Aras Kargo gönderilerinde createCommonLabel ile ortak etiket isteyip getCommonLabel ile koli başına bir ZPL etiketi indirirsiniz. Kendi kargo anlaşmanızla (whoPays = 1) çalışıyorsanız etiket kargo firmanızdan gelir.
Sonuç ve sonraki adımlar
Şu sırayla ilerleyin: kimlik bilgileri ve stage erişimi; eşleme tablolarıyla salt okunur sözlükler; batch takibiyle ürün yaratma; delta stok-fiyat senkronizasyonu; stream ile sipariş çekme; statü ve fatura çağrıları; etiket; webhook; en son iade, soru ve finans. Trendyol'u ana veri rolünden uzak tutun, her yazma işlemini doğrulayın ve changelog'a bir sahip atayın. Bağlantıyı şirket içinde yazıp sürdürmek istemiyorsanız AIOR, Trendyol entegrasyonu hizmetini proje olarak sunuyor.This guide is for developers and in-house teams who build or debug their own Trendyol seller integration instead of buying an integrator's software: connecting an ERP, a warehouse system or a web shop to Trendyol's Marketplace (Partner) API for listings, stock and price, orders, labels, invoices and returns. It follows the official documentation at developers.trendyol.com as of late October 2026. If you are reading older code or blog posts, note two changes: the docs set 15 October 2026 as the end date for the Product V1 endpoints and the old /orders listing endpoint, so new code should use only Product V2 and /v2/orders; and since 14 September 2026 product rate limits are shared per service group and scale with your listing limit. The day-to-day side of marketplace selling is covered in Selling on marketplaces: the operational side; this article stays with the API.
Own integration, integrator or file uploads?
| Option | Fits when | Watch out for |
|---|---|---|
| Your own integration (User-Agent "{sellerId} - SelfIntegration") | An ERP or WMS already owns stock and prices, a developer can maintain the code, and you need workflows packaged tools don't cover (several warehouses, bundles, your own invoicing pipeline) | Trendyol states that it does not provide code support; you track API changes yourself |
| Integrator software (entegratör) | Several marketplaces, no developers, a quick start | Subscription or usage fees, the integrator's data model, waiting for its update after a Trendyol change |
| Seller Center bulk tools or XML/Excel feed services | A small catalogue, few orders, stock that rarely changes | Manual steps, slow stock updates, oversell risk during campaigns |
Architecture: one source of truth, queues and idempotency
Treat Trendyol as a sales channel, never as the master of your data. Content, stock and price live in your ERP, PIM or database; the integration publishes changes outward and pulls orders, returns and settlements inward.
Kod:
ERP / WMS / PIM (master data: product, stock, price)
| change log or outbox: only rows that changed
v
queue ---> catalogue worker ---> Product V2 endpoints ---> batchRequestId
---> inventory worker ---> price-and-inventory ---> batchRequestId
batch tracker: polls batch results, turns FAILED items into tasks
Trendyol webhook ---> receiver (auth check, store raw event, 200 OK) ---> queue
reconciler (orders/stream every few minutes) --------------------------> queue
queue ---> order worker (idempotent on shipmentPackageId + status + lastModifiedDate)
---> ERP / WMS: reserve stock, pick, invoice, label, ship
- Mapping tables. Store your SKU next to barcode (the key for stock, price and orders), stockCode, productMainId (the "model code" that groups variants), contentId, categoryId, brandId, attribute and value IDs, address IDs and carrier codes. Most rejected batches trace back to a stale or missing mapping.
- One rate limiter per limit group. Product writes, product reads and stock-price updates each share a budget across several endpoints (see below).
- Idempotent consumers. Webhooks retry, the reconciler re-reads packages and workers crash halfway, so every handler must give the same result when it runs twice.
- Every write ends in a verification. Product and stock-price calls return a batchRequestId, not a result.
- Logs without secrets. Keep bodies with their batchRequestId or shipmentPackageId but mask the Authorization header. Order payloads hold names, addresses, phone numbers and sometimes a Turkish ID number, so apply KVKK (Law No. 6698): limited access, encryption at rest, a retention period.
- UTC inside, conversions at the edge. Timestamps are epoch milliseconds, some documented as GMT+3 and some as GMT.
Sizing the request budget
Count calls, not records. Assumptions: the lowest listing-limit tier (50,000 products), 20,000 active barcodes, about 3,000 package changes in two weeks and 60 returns awaiting a decision.| Job | Calls | What limits it |
|---|---|---|
| Full stock-price push for 20,000 barcodes | 20 requests of 1,000 items | Inventory & Price Write, 350 per minute at this tier: no constraint |
| Create 5,000 products | 5 requests plus batch polling | Data quality and approval time |
| Re-read 14 days of changed packages via the stream | About 15 requests at size 200 | Recommended 5 seconds between calls: about 75 seconds |
| Approve 60 returns, one claim per call | 60 requests | 5 per minute: about 12 minutes |
Authentication, headers and the stage environment
- Credentials: seller ID, API key and API secret are in Seller Center under Hesap Bilgilerim > Entegrasyon Bilgileri, visible only to the master (admin) user. The "Entegrasyon Referans Kodu" on the same page is requested in support tickets; keep it as secret as the key.
- Basic authentication: username = API key, password = API secret. The seller ID goes into the URL path.
- User-Agent is mandatory: "{sellerId} - SelfIntegration", or "{sellerId} - {IntegratorName}" for an integrator (alphanumeric, at most 30 characters). Requests without it are blocked with 403.
- Production: https://apigw.trendyol.com, no IP allow-listing (though Trendyol notes an IP can be blocked).
- Stage: https://stageapigw.trendyol.com with a separate account (panel: stagepartner.trendyol.com) and separate keys. Stage answers only allow-listed IPv4 egress addresses, otherwise 503. Ask through a support ticket (the docs say the IP is defined within 24 hours) or use the IP whitelist API, called on the production gateway with your production seller ID: at most 7 IPv4 addresses, 7 additions per day, each entry with an expiresAt date.
Bash:
export TY_BASE="https://apigw.trendyol.com" # stage: https://stageapigw.trendyol.com
export TY_SELLER_ID="123456" # placeholder
export TY_API_KEY="..." # from a secret store, never from git
export TY_API_SECRET="..."
curl -sS "$TY_BASE/integration/product/sellers/$TY_SELLER_ID/products/approved?page=0&size=1" \
-u "$TY_API_KEY:$TY_API_SECRET" \
-H "User-Agent: $TY_SELLER_ID - SelfIntegration" \
-H "Accept: application/json"
YAML:
trendyol:
prod:
base_url: https://apigw.trendyol.com
seller_id: ${TY_PROD_SELLER_ID}
api_key: ${TY_PROD_API_KEY}
api_secret: ${TY_PROD_API_SECRET}
stage:
base_url: https://stageapigw.trendyol.com
seller_id: ${TY_STAGE_SELLER_ID} # the stage account differs from prod
api_key: ${TY_STAGE_API_KEY}
api_secret: ${TY_STAGE_API_SECRET}
integrator_name: SelfIntegration # User-Agent: "{seller_id} - SelfIntegration"
limits_per_minute: { product_read: 1000, product_write: 200, inventory_price_write: 350, order_list: 30, claim_approve: 5 } # lowest tier
Rate limits since 14 September 2026
The Service Limitations page ties limits to your listing-limit tier (50,000, 75,000, 150,000 or 500,000 products, or unlimited):| Group | Includes | Requests per minute, lowest to unlimited tier |
|---|---|---|
| Product Integration Read | Product filters, batch result, brands, categories, attributes, values, addresses, update audits | 1,000 to 2,000 |
| Product Integration Write | Create, update, delete, archive, unlock, brand creation, buybox check | 200 to 600 |
| Inventory & Price Write | Stock and price update | 350 to 2,000 |
| Order packages | getShipmentPackages | 30 to 100 |
| Claims | Approve; create claim issue | 5 each |
Endpoint map
Paths hang off the environment's base URL.| Task (service name in the docs) | Method and path |
|---|---|
| Category tree (getCategoryTree) | GET /integration/product/product-categories |
| Category attributes v2, attribute values v2 | GET /integration/product/categories/{categoryId}/attributes, .../attributes/{attributeId}/values |
| Brands, brand creation | GET /integration/product/brands, POST /integration/product/sellers/{sellerId}/brands |
| Shipment and return addresses | GET /integration/sellers/{sellerId}/addresses |
| Create products v2 | POST /integration/product/sellers/{sellerId}/v2/products |
| Batch result (getBatchRequestResult) | GET /integration/product/sellers/{sellerId}/products/batch-requests/{batchRequestId} |
| Approved and unapproved products v2 | GET .../sellers/{sellerId}/products/approved, .../products/unapproved |
| Stock and price (updatePriceAndInventory) | POST /integration/inventory/sellers/{sellerId}/products/price-and-inventory |
| Order packages (getShipmentPackages) | GET /integration/order/sellers/{sellerId}/v2/orders |
| Order stream (getShipmentPackagesStream) | GET /integration/order/sellers/{sellerId}/orders/stream |
| Picking and Invoiced (updatePackage) | PUT /integration/order/sellers/{sellerId}/shipment-packages/{packageId} |
| Invoice link, invoice file | POST /integration/sellers/{sellerId}/seller-invoice-links, .../seller-invoice-file |
| Common label (createCommonLabel, getCommonLabel) | POST, GET /integration/sellers/{sellerId}/common-label/{cargoTrackingNumber} |
| Webhooks | /integration/webhook/sellers/{sellerId}/webhooks |
| Claims (getClaims) | GET /integration/order/sellers/{sellerId}/claims |
Catalogue: categories, attributes and product creation
Product creation (createProducts in V1, now Product V2) is asynchronous and unforgiving about reference data such as categories, attributes and brands, so build the lookups first:- Categories: only leaf categories accept products. The docs recommend refreshing the tree and the attributes weekly.
- Attributes: per category you get required, allowCustom (send free text in customAttributeValue instead of an ID), allowMultipleAttributeValues, slicer (a separate product card, typically colour) and varianter (a variant on the same card, typically size; one per category).
- Values, brands, addresses: page through the values service (size up to 1,000), resolve brandId from the brand list or create the brand, and take shipmentAddressId and returningAddressId from the addresses service.
JSON:
{
"items": [
{
"barcode": "DEMO-USBC-1M-BLK",
"title": "USB-C - USB-C Şarj Kablosu 1 m 60 W Siyah",
"productMainId": "DEMO-USBC-1M",
"brandId": 1234567,
"categoryId": 7654,
"quantity": 40,
"stockCode": "USBC-1M-BLK",
"dimensionalWeight": 1,
"description": "Örgü kaplı USB-C kablo, 1 m, 60 W şarj desteği.",
"listPrice": 299.90,
"salePrice": 249.90,
"vatRate": 20,
"images": [{ "url": "https://cdn.example.com/usbc-1m-blk-1.jpg" }],
"attributes": [
{ "attributeId": 47, "customAttributeValue": "Siyah" },
{ "attributeId": 1111, "attributeValueId": 22222 }
],
"shipmentAddressId": 111,
"returningAddressId": 222
}
]
}
Polling the batch result
The create response is just a batchRequestId, and Trendyol's FAQ is explicit that getting one does not mean success. Poll until the batch is COMPLETED; stock-price batches have no batch-level status, so check that every item is SUCCESS or FAILED. Results remain available for 4 hours.
Python:
import time
import requests
def make_session(seller_id, api_key, api_secret):
s = requests.Session()
s.auth = (api_key, api_secret)
s.headers.update({"User-Agent": f"{seller_id} - SelfIntegration",
"Accept": "application/json"})
return s
def wait_for_batch(s, base, seller_id, batch_id, timeout_s=1800):
url = f"{base}/integration/product/sellers/{seller_id}/products/batch-requests/{batch_id}"
delay, deadline = 5, time.monotonic() + timeout_s
while time.monotonic() < deadline:
r = s.get(url, timeout=30)
if r.status_code != 429:
r.raise_for_status()
body = r.json()
items = body.get("items") or []
done = body.get("status") == "COMPLETED" or (
items and all(it.get("status") in ("SUCCESS", "FAILED") for it in items))
if done:
return [(it.get("requestItem", {}).get("barcode"), it.get("status"),
it.get("failureReasons", [])) for it in items]
time.sleep(delay)
delay = min(delay * 2, 120)
raise TimeoutError(f"batch {batch_id} still in progress")
Approval, updates, archive and delete
- Successful items enter content approval. The unapproved filter shows pendingApproval or rejected; fix a rejected item and resubmit it with unapproved-bulk-update. Approved products carry onSale, notOnSale, archived, locked or blacklisted. Paging stops at page × size = 10,000; beyond that, pass the returned nextPageToken.
- Approved products are changed through content-bulk-update, variant-bulk-update (stockCode, VAT rate, addresses, desi) and delivery-info-bulk-update. Barcode, productMainId, brandId, categoryId and slicer or varianter values are fixed. Two traps: changing one attribute means sending all of them, and a field sent as null is deleted. The review outcome per contentId comes from update-audits.
- Archiving (archive-state) hides a product without deleting it. Only products awaiting approval, or approved ones archived for more than a day and not stopped by Trendyol, can be deleted. Products locked because the price looked too low or too high, after a critical price error or after unsupplied orders can be unlocked once the cause is fixed.
Stock and price sync (updatePriceAndInventory)
POST /integration/inventory/sellers/{sellerId}/products/price-and-inventory takes up to 1,000 barcodes, and an item may carry only quantity or only prices. quantity is sellable stock: Trendyol lowers it as orders arrive and replaces it with your next value, capped in the docs at 20,000 units per product. listPrice is the crossed-out price, salePrice the selling price.
JSON:
{
"items": [
{ "barcode": "DEMO-USBC-1M-BLK", "quantity": 37, "listPrice": 299.90, "salePrice": 249.90 },
{ "barcode": "DEMO-USBC-1M-WHT", "quantity": 0 },
{ "barcode": "DEMO-USBC-2M-BLK", "listPrice": 329.90, "salePrice": 289.90 }
]
}
- Send deltas. An identical body within 15 minutes is refused with "15 dakika boyunca aynı isteği tekrarlı olarak atamazsınız!" ("you cannot send the same request repeatedly for 15 minutes"). Send only barcodes changed since the last accepted push.
- Coalesce prices. Above 30 price updates per minute for one barcode, items fail; keep only the latest price per cycle.
- Back off on 429 and 5xx with exponential delays and jitter, and cap the attempts. Other 4xx responses are data problems: fix, don't retry.
- After a timeout, verify before resending. An identical resend may hit the 15-minute rule, which itself shows the first request arrived. Check the batch result or the inventory-and-price filter instead.
- Reconcile daily with the approved inventory-and-price filter (size up to 100, then nextPageToken past 10,000) and push the differences.
Python:
import hashlib, json, random, time
def push_deltas(s, base, seller_id, changes, recently_sent):
"""changes: dicts with barcode plus quantity and/or prices.
recently_sent: digest -> unix time, persisted between runs."""
url = f"{base}/integration/inventory/sellers/{seller_id}/products/price-and-inventory"
batch_ids = []
for start in range(0, len(changes), 1000):
body = {"items": changes[start:start + 1000]}
digest = hashlib.sha256(json.dumps(body, sort_keys=True).encode()).hexdigest()
if time.time() - recently_sent.get(digest, 0) < 15 * 60:
continue # identical body accepted less than 15 min ago
for attempt in range(6):
r = s.post(url, json=body, timeout=30)
if r.status_code == 429 or r.status_code >= 500:
time.sleep(min(60, 2 ** attempt) + random.random())
continue
r.raise_for_status() # other 4xx: fix the data, don't retry
batch_ids.append(r.json()["batchRequestId"])
recently_sent[digest] = time.time()
break
return batch_ids
Orders: listing, statuses, packages, invoices and labels
| Aspect | /v2/orders (getShipmentPackages) | /orders/stream (getShipmentPackagesStream) |
|---|---|---|
| Use for | Targeted lookups: an orderNumber, package IDs, a status, a recent window | Periodic sync, catching up after downtime, exports |
| Paging | page and size (max 200); at most 10,000 packages per query (pages 0 to 49 at size 200) | Opaque nextCursor and hasMore; changing the filter mid-cursor returns 400 |
| Time window | Last week by default, at most two weeks per query, data from the last month | At most 14 days per query on lastModifiedStartDate/EndDate, data from the last 3 months |
| Order and pacing | orderByField=PackageLastModifiedDate, ASC or DESC; 30 to 100 requests per minute by tier | Fixed lastModifiedDate descending; at least 5 seconds between requests recommended |
Python:
def changed_packages(s, base, seller_id, since_ms, until_ms):
url = f"{base}/integration/order/sellers/{seller_id}/orders/stream"
params = {"lastModifiedStartDate": since_ms, "lastModifiedEndDate": until_ms, "size": 200}
while True:
r = s.get(url, params=params, timeout=60)
if r.status_code == 429:
time.sleep(30)
continue
r.raise_for_status()
page = r.json()
yield from page.get("content", [])
if not page.get("hasMore"):
return
params["nextCursor"] = page["nextCursor"] # opaque: pass it back unchanged
time.sleep(5)
| Package status | Meaning and action |
|---|---|
| Awaiting, Verified | Payment check in progress: reserve stock, nothing else until Created |
| Created | Ready to ship: start fulfilment |
| Picking, Invoiced | Sent by you, in that order |
| Shipped, AtCollectionPoint | With the carrier, or waiting at a pick-up point |
| Delivered | Final |
| UnDelivered, Returned | Delivery failed; Returned (final) means it came back to you |
| Cancelled, UnSupplied | Cancelled, including unsupplied: release the reservation |
| UnPacked | Split; new packages replace it |
Picking, Invoiced, unsupplied and split
Status updates go to PUT /integration/order/sellers/{sellerId}/shipment-packages/{packageId}, Picking first, then Invoiced:
JSON:
{
"lines": [{ "lineId": 4765111111, "quantity": 1 }],
"params": { "invoiceNumber": "ABC2026000000123" },
"status": "Invoiced"
}
Invoices
The invoice comes from your e-Fatura or e-Arşiv provider; Trendyol then needs the PDF e-mailed to the order's masked address or one of two calls:- Invoice link (sendInvoiceLink): POST .../seller-invoice-links with invoiceLink, shipmentPackageId, invoiceDateTime (10-digit seconds or 13-digit milliseconds) and invoiceNumber, answered with 201. Number and date are mandatory for micro-export and Trendyol Yurt Dışı Aracılığı packages, and the number follows the 16-character pattern of three alphanumerics, a year and nine digits. The docs require the link to stay reachable for 10 years. A 409 means the package already has an invoice or the link was used for another package.
- Invoice file: POST .../seller-invoice-file as multipart form data with shipmentPackageId and file (PDF, JPEG or PNG, up to 10 MB, not future-dated).
Labels and carriers
For Trendyol-paid shipments with Trendyol Express (TEX) or Aras Kargo, request a common label with POST .../common-label/{cargoTrackingNumber} and a body such as {"format": "ZPL", "boxQuantity": 2}, then GET the same path for one ZPL label per box, ideally after Picking or Invoiced. ZPL is the only format, so you need a thermal printer that speaks it. When the order has whoPays = 1, the shipment runs on your own carrier agreement and the label comes from that carrier. cargoTrackingNumber is the CODE128 barcode used on Seller Center labels. PUT .../shipment-packages/{packageId}/cargo-providers with a code such as YKMP, ARASMP or TEXMP switches carrier, once per package every 5 minutes; a switch to TEX fails when your TEX quota is full.Webhooks for order packages
A webhook pushes the full package, in the getShipmentPackages model, whenever it reaches a subscribed status: CREATED, PICKING, INVOICED, SHIPPED, CANCELLED, DELIVERED, UNDELIVERED, RETURNED, UNSUPPLIED, AWAITING, UNPACKED, AT_COLLECTION_POINT or VERIFIED.- Create: POST /integration/webhook/sellers/{sellerId}/webhooks with url, authenticationType (BASIC_AUTHENTICATION with username and password, or API_KEY with apiKey, delivered in the x-api-key header) and subscribedStatuses; an empty list means every status, including future ones. GET lists them; PUT or DELETE .../webhooks/{id} updates or removes one; .../activate and .../deactivate switch it.
- Limits: 15 webhooks per seller, passive ones included; the URL must not contain "Trendyol", "Dolap" or "localhost".
- Retries: failed deliveries are retried every 5 minutes until they succeed (the docs say this will change). After repeated failures (13, per the FAQ) the webhook turns passive and two e-mails go out; fix the receiver and reactivate it.
Python:
import hmac, json, os
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_KEY = os.environ["TY_WEBHOOK_KEY"] # the apiKey you registered
@app.post("/hooks/trendyol/packages")
def trendyol_packages():
if not hmac.compare_digest(request.headers.get("x-api-key", ""), WEBHOOK_KEY):
abort(401)
body = request.get_json(silent=True) or {}
# The docs show the paged getShipmentPackages envelope; accept a bare package too.
packages = body["content"] if isinstance(body.get("content"), list) else [body]
for pkg in packages:
# unique key (shipmentPackageId, status, lastModifiedDate), INSERT ... ON CONFLICT DO NOTHING
save_event_once(pkg["shipmentPackageId"], pkg.get("status"),
pkg.get("lastModifiedDate") or 0, json.dumps(pkg))
return "ok", 200
Returns (claims)
- List (getClaims): filter by claimItemStatus (Created, WaitingInAction, Accepted, Cancelled, Rejected, Unresolved, InAnalysis), by startDate/endDate on lastModifiedDate, or by claimIds; query Created for new claims. claimDate is GMT, three hours behind Seller Center.
- Approve (approveClaimLineItems): PUT .../claims/{claimId}/items/approve with claimLineItemIdList, only in WaitingInAction. Some approvals pass through WaitingFraudCheck before Accepted.
- Reject (createClaimIssue): take a reason from GET /integration/order/claim-issue-reasons, then POST .../claims/{claimId}/issue with claimIssueReasonId, claimItemIdList and a description (up to 500 characters) as query parameters and evidence as multipart files (5 MB each). Files are mandatory except for a few reasons such as 1651 (parcel never arrived), which is blocked in the first 24 hours of WaitingInAction.
- Load reasons, don't hard-code them: on 8 October 2026 Trendyol retired several (251 and 1701 among them) and added 2077, 2078 and 2079.
- Queue both actions: approve and issue allow 5 requests per minute each.
Customer questions and finance, briefly
Questions: GET /integration/qna/sellers/{sellerId}/questions/filter?status=WAITING_FOR_ANSWER (last week by default, two weeks at most per query, size up to 50); answer with POST .../questions/{id}/answers and {"text": "..."} of 10 to 2,000 characters, checked for banned words before publication.Finance: under /integration/finance/che/sellers/{sellerId}/ you get settlements (sales, returns, discounts, coupons, commission corrections; transactionType or transactionTypes required), otherfinancials (payments, transfers, invoices, deductions) and payment-order (paid payment orders, 10 per page). Filter by a date range of at most 15 days, by paymentDate, or by paymentOrderId, the key for matching a bank payment to its orders. Records appear after delivery; the docs say payment orders are created every Wednesday, with exceptions, for orders due that week. Limit: 100 requests per minute.
Testing and go-live checklist
On stage, POST /integration/test/order/orders/core creates a test order (stage seller ID in a SellerID header, authenticated as that seller; commercial for a corporate invoice, microRegion for micro export), and PUT /integration/test/order/sellers/{sellerId}/shipment-packages/{packageId}/status moves it through Shipped, AtCollectionPoint, Delivered, UnDelivered or Returned, forward only. Two more stage-only services move a test claim to WaitingInAction and create a customer question.- User-Agent on every request; keys from a secret store; prod and stage settings cannot mix.
- Leaf categories only; attribute caches refreshed weekly; every SKU mapped to barcode, productMainId and contentId.
- Every write tracked to its batch result; FAILED items reach a person.
- Delta stock-price pushes, no identical body within 15 minutes, one limiter per group, backoff on 429.
- Stock buffer defined; Awaiting orders reserve stock but are not shipped.
- Orders by webhook plus stream reconciliation; idempotent processing; replaced packages handled.
- Picking, Invoiced, invoice link or file, label and shipment tested end to end on stage, including a 409.
- Claims queued at 5 requests per minute, reasons loaded from the service.
- Alerts on bursts of 401, 403, 429 and 5xx, on FAILED items and on the "webhook deactivated" e-mail.
- An owner for the changelog and the API status page (Api HealthCheck in the docs).
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 ClientApiAuthenticationException | Wrong key or secret, stage keys against prod (or the reverse), key and secret swapped | Check the environment; username = key, password = secret |
| 403 | Missing User-Agent; the error-code page also cites a wrong endpoint or parameter | Send "{sellerId} - SelfIntegration" everywhere; compare the path with the docs |
| 503 on stage only | Egress IP not allow-listed, or its entry expired | Add the IPv4 by ticket or whitelist API; watch expiresAt |
| Prod timeouts from a server abroad | Case named in the English docs for providers outside Turkey | Open a Partner Support ticket with your IP |
| 429 too.many.requests | Group limit, or 50 requests per 10 seconds on one endpoint | Per-group limiter, backoff, fewer and fuller batches |
| 426 or "scheduled brownout" | Retired endpoints (Product V1, old /orders) | Migrate to Product V2 and /v2/orders |
| "15 dakika boyunca aynı isteği..." | Identical stock-price body within 15 minutes | Send deltas; verify the earlier batch |
| Batch item FAILED | Attribute missing or invalid, non-leaf category, image URL, listPrice below salePrice, bad barcode | Read failureReasons, refresh lookups, resubmit |
| Orders missing from /v2/orders | Outside the two-week or one-month window, or past 10,000 packages | Use the stream or narrower windows |
| Times three hours off | orderDate and query dates documented as GMT+3; packageHistories.createdDate and claimDate as GMT | Normalise to UTC on input; check once with a stage order of known time |
| Package ID no longer valid | A partial cancellation or split replaced it | Re-read by orderNumber; follow originPackageIds |
| 409 on the invoice link | Already invoiced, or the link was used elsewhere | Check invoiceStatus; one unique link per package |
| Webhook passive | Repeated delivery failures | Fix the receiver, call .../activate, re-read the gap via the stream |
FAQ
Do I need an integrator to connect to Trendyol?No. Any seller can call the API with its own keys and the "SelfIntegration" User-Agent. An integrator makes sense across several marketplaces without in-house developers. Either way, Trendyol states that it does not provide code support.
Is there a sandbox?
Yes: stage, at stageapigw.trendyol.com, with its own account, keys and IP allow-listing. You can create test orders, move them through delivery statuses, push test claims to WaitingInAction and create customer questions there.
How often should I sync stock?
Push changes as they happen in small delta batches and reconcile fully once a day. Trendyol publishes no fixed interval; the practical limits are the group rate limit, the 15-minute duplicate rule and 30 price updates per minute per barcode.
Can I receive orders by webhook only?
Not advisable. Trendyol recommends periodic syncing alongside webhooks, and in 2026 its changelog asked sellers more than once to re-query order windows after incidents.
How do Trendyol Express labels work?
For TEX or Aras Kargo shipments paid by Trendyol, request a common label with createCommonLabel and download the ZPL with getCommonLabel, one label per box. With your own carrier agreement (whoPays = 1), the label comes from your carrier.
Conclusion and next steps
Build in this order: credentials and stage access; read-only lookups with mapping tables; product creation with batch tracking; delta stock-price sync; order pulling via the stream; status and invoice calls; labels; webhooks; then claims, questions and finance. Keep Trendyol out of the master-data role, verify every write and give the changelog an owner. If you would rather not build and maintain the connector in house, AIOR offers Trendyol integration as a project service.
Son düzenleyen: